#4 v1.0

Нээсэн
xuyiping 10-ийг xuyiping/feature/event-оос xuyiping/develop руу нэгтгэхийг хүсэлт

+ 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)

+ 1 - 1
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-20250624095218-69693701e5c3
 	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

+ 6 - 138
go.sum

@@ -36,144 +36,12 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
 cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250416085805-501c91e70d45 h1:O4vnwyLdVPFXGoiqfQqeaYAzdgTwwwphyVJ/0ws/UqU=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250416085805-501c91e70d45/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250416102510-604a13fe9d52 h1:aCqqT2Y/po3OtkUqzogDOIjqwny03iu1rp47CU1yiz0=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250416102510-604a13fe9d52/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250418054121-409ac98e3475 h1:zGCW7bHN9o67VxxK3eZr0k0h5HB+twDjx4cIc9q3dEI=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250418054121-409ac98e3475/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250420125055-0d4da4a5a6fe h1:FSRq0AItdCCIisvxG0Ki+l7PxxaPFJSkOXN49x3LpnQ=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250420125055-0d4da4a5a6fe/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250421082915-83b5518d3a0c h1:CuiDP9SfrSEiJ/wG0iz7wgIt9n38SeckUXscY6X5zBU=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250421082915-83b5518d3a0c/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250423062840-8ac2189a39de h1:HNb5GAbIF+e9lDQ4e+AUSzmwQxpoEEsCbS0iDuC536Y=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250423062840-8ac2189a39de/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250423064947-57beca75393d h1:1UsWm3g4/DXLG8c+GrzYGFLKnFAfDLsV+fvPMryd9CU=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250423064947-57beca75393d/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250423065632-fc64fb76f946 h1:K41rOlpITHu+tn2SINKpBpk052Txj67l50/hQtXeZ7A=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250423065632-fc64fb76f946/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250423070335-3521c22802be h1:fcqMFBsu9P1YwMjGqOh8NppxhBWTvu0sbOfrjmDm6no=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250423070335-3521c22802be/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250423080234-2c477a4a0bec h1:iOaGH+q4slB4oUliGCXVnmBDGLM/+80A2+7VSWPdp0U=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250423080234-2c477a4a0bec/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250424024203-073d6537225b h1:GUHRd7BvPy/dL+heSFd1r16Y3yZlnzqsD/FHDHAOfsg=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250424024203-073d6537225b/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250424025835-b9c007f15d44 h1:bxYqBSegVy9cVdjTtjvLKK4HVT40moltROoaHVXNmZM=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250424025835-b9c007f15d44/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250425011443-a83a84c81b7d h1:/2tQo6LnyvkvN+6ZhD/n5243lDvA25dcc35q+xbCl7A=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250425011443-a83a84c81b7d/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250425013204-c2e3c2cb22b5 h1:9HdkH0TLT69nYrSpVNpK+AqRsEj6zsSAI0wyn3txEJo=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250425013204-c2e3c2cb22b5/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250425022013-aea428d7468b h1:HsSfifHMvaNok/W1Q22axMJuYDSkpFnpFAIoO4Qo0NA=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250425022013-aea428d7468b/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250425025152-491564f7b245 h1:WSBs/sMmKJBm+GBlFrvlAy9hYDgnxOSo9sFFsqdkvpo=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250425025152-491564f7b245/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250425035842-15ee838b15e8 h1:8EsgPwBQn44Ntxxk8Ysat0IAAlFaEGwl/fszj38I+wc=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250425035842-15ee838b15e8/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250427081352-93a6cd4b4694 h1:S4N8wO1YcY7Up+fPH6ggDsVaqRDxWKP3wT36m5sH3Ac=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250427081352-93a6cd4b4694/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250427084730-8129eb58bb83 h1:BlREMV1B0/dRYN5Z9ju6FWzGnx6caPxzqwL/aMY/+XE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250427084730-8129eb58bb83/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250427091240-4d1fa51de52c h1:oRoz0BJWx7zMsXWA1x/2EILg9e0iPFprs23NWDcU2zA=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250427091240-4d1fa51de52c/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250428030038-1fa928429b8b h1:hBfSqTM9XibBmMG5y7X3rQx2uvPJZge/SP/HP0/FDXA=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250428030038-1fa928429b8b/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250428072657-d9a291b08ae9 h1:UQV4c+yPVVz50fDyCUweeezb3crtIV05nJq9cAGvPEs=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250428072657-d9a291b08ae9/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250428074830-fb40d5445257 h1:zDyc5DgdWguRUqWYAL5nbRC3o5G30x+tRdjD+4VTUS0=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250428074830-fb40d5445257/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250429024839-b282b4e1465a h1:slKL5wf3QH5ufX5CkJ+HDqkFjdp7VEujNV9C+q7nAbw=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250429024839-b282b4e1465a/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250429032832-f43ab362a21b h1:9Shzi/Jp8uHcb47IVRtbuWPqbgv483UJaxsEoy3RD5s=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250429032832-f43ab362a21b/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250430023822-7c5bf763451e h1:IWZC/FOxItSt33n+qzMdJhclW9oFzOnrze0SGC3jT+c=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250430023822-7c5bf763451e/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250513060115-1841a323de9e h1:w+6CKL+iJYBwsmaLIUby7xeqlZKHlbL+GV/BeiEZBYo=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250513060115-1841a323de9e/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250514013843-42f268c5cc98 h1:xAEEFgktRY0mQWtHcM4czFrR0ArCTH/5g2oQw2+qe5o=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250514013843-42f268c5cc98/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250514015819-8b4ba0765b3c h1:F1lFnBnt1AIPzERyzGOGuMjZLEUkU2XDEeBH2MKMlK4=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250514015819-8b4ba0765b3c/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250514022148-95b29436d061 h1:hKrOp5DfZp2F+VPG44YKP7DcmgcvAYz14yx7lLrMNMQ=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250514022148-95b29436d061/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250514080018-cb20fb9800f8 h1:FWbFRaJGr2cvOyr7hbwC3DFBKWM1Y+5+UK+xliShcpU=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250514080018-cb20fb9800f8/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250514085954-501d0d6cb26c h1:PkMErJiu4y8ft7jgjzVkfnV3OnqT2tKer5YnjS6fMRc=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250514085954-501d0d6cb26c/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250515070041-363e94a8dbac h1:i6cw9nC8OCbBR+3lB5XX8o4NabcVR6gJ04dbEQxkmC0=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250515070041-363e94a8dbac/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250521020200-04806a79ad46/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250521021325-89e4fc71dd39 h1:/7MllucrQJnIxXY9fapeEOucVejTYY1IxsyIe8tLhlA=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250521021325-89e4fc71dd39/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250521031814-2eb66df61c58 h1:PDG62cMpWaD/V11wty6sjiM4t2/sYFBg9u2S1tiZWbs=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250521031814-2eb66df61c58/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250523054938-90895b21e2e6 h1:g/L5Dr4bkiWijpYC15mUrwBh+VSE400njGEvzgkmwpk=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250523054938-90895b21e2e6/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250523062919-b893047f10c8 h1:j8gu75Sg2xvbC+dZ0UGFQMcNnU8AmBQ41KRgHXOcPik=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250523062919-b893047f10c8/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250526035313-54b0f5e98983 h1:KWF+5I/HFeT27kiqn9DNhQrV4kIC9uxas9aalU7/LAE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250526035313-54b0f5e98983/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250526062319-25f58c4f9504 h1:0CUkSlzaAkVhwqWFdGRimK9WdycyDpeDPiMg6M2pBLI=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250526062319-25f58c4f9504/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250526073830-beed24bc2e26 h1:XzWzg5AXdJYJeiS+jwfqapmIbrJ92ETi/qZ3iqjrC4c=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250526073830-beed24bc2e26/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250526081009-3766cd67d900 h1:+QN7wEwmtLwY89R3iE6kMLgp/jS1QgsPczJdZUCoFe8=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250526081009-3766cd67d900/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250526084133-ef8acddd1868 h1:JTXSBH8VO9uUweNyEy5o/OngSJ4xx2WmYVBn+/1vp1w=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250526084133-ef8acddd1868/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250526090502-532d86d767de h1:+jPEpEljv/JyEPKFuNCeDbqbnyjCoGB14fucLMX+nv0=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250526090502-532d86d767de/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250526091836-9e663a273679 h1:Ulh1ArxGRApui9E5UVNbCa3Cv3NnZIs3vbIpNy/3lA0=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250526091836-9e663a273679/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250526094651-0cef600addd0 h1:nGmhS9BKCMgCz9zEVmygDIPNrolI9lb9uLV6NfoTxWE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250526094651-0cef600addd0/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250530040106-85ff23fe88f6 h1:UjId/C6diIwgYo/gh5cqVNpa9TSYyAsi1H/zlMqy/vs=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250530040106-85ff23fe88f6/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250530051007-6ce1b83221af h1:3SBlTKUWyBz4l86klizGDt/xda/7j9PGsRiUyZb+yX8=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250530051007-6ce1b83221af/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250530090709-1e32a1ade71d h1:upETgWdn+raGyxXdP1S70Z4gMuGZep9RjcLsWLIWeF0=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250530090709-1e32a1ade71d/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250603022546-465f63606fa5/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250603023510-4787586eb99f h1:ESNXR2m+12UOrrBMD1EnC732hf2H4LEwEhON/n1p6RU=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250603023510-4787586eb99f/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250603030738-6435257183f9 h1:WDBhCOK21ffgnRsgrjYZHQY7LQ3nboMZFJ3P8BYEokQ=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250603030738-6435257183f9/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250603061552-ea096c7a318d h1:9pPdB8ogY6KxRxjCJNMTss2IpsCAivGxEwWcRxBkGfA=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250603061552-ea096c7a318d/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250603093243-82ebd7095700 h1:4s7hJ6Vr2FUydlb8gglDoJaPixKD4hnpvN78COUlAsw=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250603093243-82ebd7095700/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250603093658-9b2144518ce7 h1:hAbUSi6s55gRk7nJfWq+d66rN5phpOggRqKxyaj1T/M=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250603093658-9b2144518ce7/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250609030041-2973960e699d h1:OSpVELaFRbVykh3MJ4kfHaB6zd8b6Ibj22jDIl92nrU=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250609030041-2973960e699d/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250609031915-1c289a676298 h1:oqXkLG8jwAxVl7uTF1o/C2MQVzhd7NrAL/ZNb3FyGQU=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250609031915-1c289a676298/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250609060156-a9a9e557016e h1:xDIjht8Ol7BICYL3+Y9WZmM8JSM5bO+X8L09RskPkW0=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250609060156-a9a9e557016e/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250611092629-ca524a76d63c h1:qmsW30h4rIb9gmyHp+zgIK58TIyFElUo9ntH0SRgU2Y=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250611092629-ca524a76d63c/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250612024540-e99718a813f5 h1:wamhokmjikNkC3y4VPhgCGHZMmiRdIFykEm4nEucjvA=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250612024540-e99718a813f5/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250612030131-3c8b446ca813 h1:ttDGhXoyHlmBy+R7WiiDU95VjVYjZfR2sx16VQ5B9KI=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250612030131-3c8b446ca813/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250613015007-39c38d700b04 h1:sl0O3loQUllFaQKpEWzLxYo6X3JSA1XkkZyG1+jAelA=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250613015007-39c38d700b04/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250613080456-430ffc4a0af3 h1:c5JtCkem6L1VsCw3QkkjLBrmVhWOOZfkcOhV23aVnKo=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250613080456-430ffc4a0af3/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250616015302-2c053d0c13a9 h1:wJWnnwiFEk/Xz5sKTEFBZfzVdmIoK558cZ+WE6YR41o=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250616015302-2c053d0c13a9/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250616065021-b2676e19b8b0 h1:f0mTagOzCakrey5Y945Kz1ws1pRuZJTTZtGSSpKIsjA=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250616065021-b2676e19b8b0/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250616072406-218faa46b2cf h1:ttzKwwveo/hL43KVVFXipdXsQnFXbOM5zNkyi0ZNqdQ=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250616072406-218faa46b2cf/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250616072843-0099184eb8ca h1:VrwccBb2bAZdXR+GZhUALHs8Jb79qKbBa1dn6MNcZGs=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250616072843-0099184eb8ca/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250616080546-3ebf4d3f0874 h1:jD/wa9PorrqH0TDiasHboBdLgmlXw1oFOc0Fly7Fw/s=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20250616080546-3ebf4d3f0874/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/pkg v0.0.0-20241108060137-caea58c59f5b h1:w05MxH7yqveRlaRbxHhbif5YjPrJFodRPfOjYhXn7Zk=
-gitee.com/xuyiping_admin/pkg v0.0.0-20241108060137-caea58c59f5b/go.mod h1:8tF25X6pE9WkFCczlNAC0K2mrjwKvhhp02I7o0HtDxY=
-gitee.com/xuyiping_admin/pkg v0.0.0-20250514071642-f92d2ac9a85d h1:vBXmMRggF7mZVPGRDgavZ87igJgkezwX0a3v1/XtIMQ=
-gitee.com/xuyiping_admin/pkg v0.0.0-20250514071642-f92d2ac9a85d/go.mod h1:8tF25X6pE9WkFCczlNAC0K2mrjwKvhhp02I7o0HtDxY=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250624035747-6854c41ebc86 h1:UblLdMMaG8umGXUEDj23Uv4zO5ireKh4tm5FSDqTiVo=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250624035747-6854c41ebc86/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250624083202-b4e70fad3c6f h1:KfgRw6vaWKPJX3w6lOOUSlnJDLelE24YAVWBvsu9UrI=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250624083202-b4e70fad3c6f/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250624095218-69693701e5c3 h1:vc+2kAaoBLBnA6oY1yyVVlhK2xc5ea6IH9F5+jbz62A=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250624095218-69693701e5c3/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
 gitee.com/xuyiping_admin/pkg v0.0.0-20250613101634-36c36a2d27d0 h1:ZCOqEAnGm6+DTAhACigzWKbwMKtleb8/7OzP2xfHG7g=
 gitee.com/xuyiping_admin/pkg v0.0.0-20250613101634-36c36a2d27d0/go.mod h1:8tF25X6pE9WkFCczlNAC0K2mrjwKvhhp02I7o0HtDxY=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=

+ 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)
+}

+ 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

+ 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)
 	}
 }

+ 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
 }

+ 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),

+ 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 {

+ 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,
+	}
+}

+ 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").

+ 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),

+ 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 {

+ 30 - 14
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)
 		}

+ 1 - 1
module/backend/event_base_more.go

@@ -297,7 +297,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)
 				}

+ 2 - 0
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 {

+ 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
+}

+ 2 - 0
module/backend/interface.go

@@ -258,6 +258,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

+ 4 - 3
module/backend/neck_ring_warning.go

@@ -80,12 +80,13 @@ func (s *StoreEntry) NeckRingWarningEstrusOrAbortionCowList(ctx context.Context,
 		}
 	}
 
+	list := model.NeckRingEstrusWarningSlice(neckRingEstrusList).ToPB(cowMap, eventLogMap, req.MatingWindowPeriod)
 	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:    int32(len(list)),
 			PageSize: pagination.PageSize,
 			Page:     pagination.Page,
 		},
@@ -300,7 +301,7 @@ func (s *StoreEntry) NeckRingNoDiseaseBatch(ctx context.Context, req *pasturePb.
 }
 func (s *StoreEntry) EstrusWarningQuery(ctx context.Context, pastureId int64) (*gorm.DB, error) {
 	nowTime := time.Now().Local()
-	startTime := time.Unix(util.TimeParseLocalUnix(nowTime.AddDate(0, 0, -1).Format(model.LayoutDate2)), 0).Format(model.LayoutTime)
+	startTime := time.Unix(util.TimeParseLocalUnix(nowTime.Format(model.LayoutDate2)), 0).Format(model.LayoutTime)
 	entTime := time.Unix(util.TimeParseLocalEndUnix(nowTime.Format(model.LayoutDate2)), 0).Format(model.LayoutTime)
 	systemBasic, err := s.FindSystemBasic(ctx, pastureId, model.EstrusWaringDays)
 	if err != nil {

+ 87 - 29
module/crontab/neck_ring_calculate.go

@@ -47,7 +47,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
 	}
@@ -67,7 +66,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 +170,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 +180,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",
@@ -330,12 +330,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,34 +371,94 @@ 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))
 		}
 	}
 }