瀏覽代碼

analysis: 销售牛只报告

Yi 4 月之前
父節點
當前提交
9ec5589e26

+ 1 - 1
go.mod

@@ -3,7 +3,7 @@ module kpt-pasture
 go 1.17
 
 require (
-	gitee.com/xuyiping_admin/go_proto v0.0.0-20241108091551-7dc4ce1f1dd1
+	gitee.com/xuyiping_admin/go_proto v0.0.0-20241112033919-d1548b594232
 	gitee.com/xuyiping_admin/pkg v0.0.0-20241108060137-caea58c59f5b
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/eko/gocache v1.1.0

+ 12 - 0
go.sum

@@ -42,6 +42,18 @@ gitee.com/xuyiping_admin/go_proto v0.0.0-20241108073749-0d4badc06e82 h1:p0dlGbsR
 gitee.com/xuyiping_admin/go_proto v0.0.0-20241108073749-0d4badc06e82/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20241108091551-7dc4ce1f1dd1 h1:3RbPPejz79D12OMSuUgm1z5Ci15hGRfXigUE5lZkGz8=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20241108091551-7dc4ce1f1dd1/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241111083814-39ea770c4d90 h1:AOE2HjFxTB+XYPdXd/mmp1xgf+Xp4peocezeHWMq0FU=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241111083814-39ea770c4d90/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241111092802-63701e01d5d9 h1:s6KZCEu5Cfr6v/FvjqmkSydLXYTVk3DiBSfhocqeiu4=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241111092802-63701e01d5d9/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241112021117-9e90bbaa3afe h1:wJIkTHfJPBs50mmt5VPqgB+WLle15RivFwqCByN7wL4=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241112021117-9e90bbaa3afe/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241112030243-a44c7ab062f8 h1:YQNkFjMAKyUxnC76Mpg6fRpSELZ+1m7nZlJz2rtL0r4=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241112030243-a44c7ab062f8/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241112031751-6a55598719ad h1:x6hPcphIrMJralgkpvxU0INiDx2AM1QjMTxcQYrmciQ=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241112031751-6a55598719ad/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241112033919-d1548b594232 h1:kERughSYPdXPKXcA/xXPXxxIlfCiV7R3sI7TcjWC438=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241112033919-d1548b594232/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
 gitee.com/xuyiping_admin/pkg v0.0.0-20241108060137-caea58c59f5b h1:w05MxH7yqveRlaRbxHhbif5YjPrJFodRPfOjYhXn7Zk=
 gitee.com/xuyiping_admin/pkg v0.0.0-20241108060137-caea58c59f5b/go.mod h1:8tF25X6pE9WkFCczlNAC0K2mrjwKvhhp02I7o0HtDxY=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=

+ 24 - 0
http/handler/analysis/analysis.go

@@ -225,6 +225,30 @@ func DiseaseCureReport(c *gin.Context) {
 	ginutil.JSONResp(c, res)
 }
 
+func SaleCowReport(c *gin.Context) {
+	var req pasturePb.SaleCowReportRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.StartDay, valid.Required),
+		valid.Field(&req.EndDay, valid.Required),
+		valid.Field(&req.AnalysisMethod, valid.Required),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	res, err := middleware.Dependency(c).StoreEventHub.OpsService.SaleCowReport(c, &req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, res)
+}
+
 func SingleFactorInfantSurvivalRate(c *gin.Context) {
 	var req pasturePb.SingleFactorPregnancyRateRequest
 	if err := ginutil.BindProto(c, &req); err != nil {

+ 47 - 0
http/handler/goods/drugs.go

@@ -119,3 +119,50 @@ func MedicalEquipmentCreateOrUpdate(c *gin.Context) {
 		Data: &operationPb.Success{Success: true},
 	})
 }
+
+func NeckRingList(c *gin.Context) {
+	var req pasturePb.SearchNeckRingRequest
+	if err := ginutil.BindProto(c, &req); 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.NeckRingLogList(c, &req, pagination)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, res)
+}
+
+func NeckRingCreateOrUpdate(c *gin.Context) {
+	var req pasturePb.NeckRingCreateRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.Items, valid.Required),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.NeckRingLogCreateOrUpdate(c, &req); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}

+ 1 - 0
http/route/analysis_api.go

@@ -22,6 +22,7 @@ func AnalysisAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		pastureRoute.POST("/pregnancy/report", analysis.PregnancyReport)
 		pastureRoute.POST("/calving/report", analysis.CalvingReport)
 		pastureRoute.POST("/disease/cure/report", analysis.DiseaseCureReport)
+		pastureRoute.POST("/sale/cow/report", analysis.SaleCowReport)
 		pastureRoute.POST("/single/factor/pregnant/report", analysis.SingleFactorInfantSurvivalRate)
 		pastureRoute.POST("/multi/factor/pregnant/report", analysis.MultiFactorInfantSurvivalRate)
 	}

+ 2 - 0
http/route/goods_api.go

@@ -17,5 +17,7 @@ func GoodsManageAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		goodsRoute.POST("/drugs/createOrUpdate", goods.DrugsCreateOrUpdate)
 		goodsRoute.POST("/medical/equipment/list", goods.MedicalEquipmentList)
 		goodsRoute.POST("/medical/equipment/createOrUpdate", goods.MedicalEquipmentCreateOrUpdate)
+		goodsRoute.POST("/neck/ring/list", goods.NeckRingList)
+		goodsRoute.POST("/neck/ring/createOrUpdate", goods.NeckRingCreateOrUpdate)
 	}
 }

+ 25 - 0
model/event_sale.go

@@ -0,0 +1,25 @@
+package model
+
+type EventSale struct {
+	Id            int64  `json:"id"`
+	DealerId      int32  `json:"dealerId"`
+	DealerName    string `json:"dealerName"`
+	VehicleId     int32  `json:"vehicleId"`
+	SalePrice     int32  `json:"salePrice"`
+	SaleAllWeight int32  `json:"saleAllWeight"`
+	SaleAllAmount int32  `json:"saleAllAmount"`
+	SaleCowCount  int32  `json:"saleCowCount"`
+	CowIds        string `json:"cowIds"`
+	SaleAt        int64  `json:"saleAt"`
+	Remarks       string `json:"remarks"`
+	OperationId   int32  `json:"operationId"`
+	OperationName string `json:"operationName"`
+	MessageId     int64  `json:"messageId"`
+	MessageName   string `json:"messageName"`
+	CreatedAt     int64  `json:"createdAt"`
+	UpdatedAt     int64  `json:"updatedAt"`
+}
+
+func (e *EventSale) TableName() string {
+	return "event_sale"
+}

+ 20 - 0
model/event_sale_vehicle.go

@@ -0,0 +1,20 @@
+package model
+
+type EventSaleVehicle struct {
+	Id              int64  `json:"id"`
+	DealerId        int32  `json:"dealerId"`
+	DealerName      string `json:"dealerName"`
+	SaleAt          int64  `json:"saleAt"`
+	VehicleNumber   string `json:"vehicleNumber"`
+	VehicleCowCount int32  `json:"vehicleCowCount"`
+	CowIds          string `json:"cowIds"`
+	Photo1          string `json:"photo1"`
+	Photo2          string `json:"photo2"`
+	Photo3          string `json:"photo3"`
+	CreatedAt       int64  `json:"createdAt"`
+	UpdatedAt       int64  `json:"updatedAt"`
+}
+
+func (e *EventSaleVehicle) TableName() string {
+	return "event_sale_vehicle"
+}

+ 63 - 0
model/neck_ring_log.go

@@ -0,0 +1,63 @@
+package model
+
+import (
+	"time"
+
+	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+)
+
+type NeckRingLog struct {
+	Id            int64                         `json:"id"`
+	Number        string                        `json:"number"`
+	CowId         int64                         `json:"cowId"`
+	WearAt        int64                         `json:"wearAt"`
+	UnbindAt      int64                         `json:"unbindAt"`
+	Status        pasturePb.NeckRingStatus_Kind `json:"status"`
+	ErrorReason   string                        `json:"errorReason"`
+	OperationId   int32                         `json:"operationId"`
+	OperationName string                        `json:"operationName"`
+	CreatedAt     int64                         `json:"createdAt"`
+	UpdatedAt     int64                         `json:"updatedAt"`
+}
+
+func (n *NeckRingLog) TableName() string {
+	return "neck_ring_log"
+}
+
+func NewNeckRingLog(number string, cowId int64, currentUser *SystemUser) *NeckRingLog {
+	return &NeckRingLog{
+		Number:        number,
+		CowId:         cowId,
+		WearAt:        time.Now().Unix(),
+		Status:        pasturePb.NeckRingStatus_Bind,
+		OperationId:   int32(currentUser.Id),
+		OperationName: currentUser.Name,
+	}
+}
+
+func NewNeckRingLogList(req []*pasturePb.NeckRingCreateItem, currentUser *SystemUser) []*NeckRingLog {
+	res := make([]*NeckRingLog, len(req))
+	for i, v := range req {
+		res[i] = NewNeckRingLog(v.Number, int64(v.CowId), currentUser)
+	}
+	return res
+}
+
+type NeckRingLogSlice []*NeckRingLog
+
+func (n NeckRingLogSlice) ToPB(neckRingStatus map[pasturePb.NeckRingStatus_Kind]string) []*pasturePb.SearchNeckRingList {
+	res := make([]*pasturePb.SearchNeckRingList, len(n))
+	for i, v := range n {
+		res[i] = &pasturePb.SearchNeckRingList{
+			Id:           int32(v.Id),
+			Number:       v.Number,
+			CowId:        int32(v.CowId),
+			WearAtFormat: time.Unix(v.WearAt, 0).Format(LayoutDate2),
+			WearDays:     int32(time.Now().Sub(time.Unix(v.WearAt, 0)).Hours() / 24),
+			Status:       v.Status,
+			StatusName:   neckRingStatus[v.Status],
+			ErrorReason:  v.ErrorReason,
+		}
+	}
+	return res
+}

+ 14 - 0
model/sale_dealer.go

@@ -0,0 +1,14 @@
+package model
+
+type SaleDealer struct {
+	Id        int64  `json:"id"`
+	Name      string `json:"name"`
+	Phone     string `json:"phone"`
+	Address   string `json:"address"`
+	CreatedAt int64  `json:"createdAt"`
+	UpdatedAt int64  `json:"updatedAt"`
+}
+
+func (s *SaleDealer) TableName() string {
+	return "sale_dealer"
+}

+ 62 - 0
module/backend/analysis_other.go

@@ -372,3 +372,65 @@ func processDiseaseCureRateList(diseaseCureRateList1, diseaseCureRateList2 []*pa
 		}
 	}
 }
+
+func (s *StoreEntry) SaleCowReport(ctx context.Context, req *pasturePb.SaleCowReportRequest) (*pasturePb.SaleCowReportResponse, error) {
+	lastDayOfMonth, err := util.GetLastDayOfMonth(req.EndDay)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	endDayTimeUnix := util.TimeParseLocalEndUnix(lastDayOfMonth)
+	startDayTimeUnix := util.TimeParseLocalUnix(fmt.Sprintf("%s-01", req.StartDay))
+	if startDayTimeUnix == 0 || endDayTimeUnix == 0 || endDayTimeUnix <= startDayTimeUnix {
+		return nil, xerr.Custom("开始时间不能大于结束时间")
+	}
+
+	list := make([]*pasturePb.SaleCowReportList, 0)
+	pref := s.DB.Model(new(model.EventSale)).
+		Select(
+			`SUM(sale_cow_count) AS sale_all_count,
+			SUM(sale_all_amount) AS sale_all_amount,
+			SUM(sale_cow_count) AS sale_all_count,
+			SUM(sale_all_weight) AS sale_all_weight`,
+		).Where("sale_at BETWEEN ? AND ?", startDayTimeUnix, endDayTimeUnix)
+	if req.AnalysisMethod == pasturePb.SaleCowAnalysisMethod_Months {
+		pref.Select(`DATE_FORMAT(FROM_UNIXTIME(sale_at), '%Y-%m') AS statisticMethod`)
+	}
+
+	if req.AnalysisMethod == pasturePb.SaleCowAnalysisMethod_Dealer {
+		pref.Select(`dealer_name as statisticMethod`)
+	}
+
+	if err = pref.Group("statisticMethod").Order("statisticMethod ASC").Find(&list).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	chart := &pasturePb.SaleCowReportChart{
+		Headers:           make([]string, 0),
+		SaleMount:         make([]float32, 0),
+		SaleWeight:        make([]float32, 0),
+		SaleCount:         make([]int32, 0),
+		AverageSaleWeight: make([]float32, 0),
+	}
+	for i, v := range list {
+		if v.SaleAllCount > 0 && v.SaleAllWeight > 0 {
+			v.SaleAvgWeight = float32(util.RoundToTwoDecimals(float64(v.SaleAllWeight / float32(v.SaleAllCount))))
+		}
+		chart.Headers = append(chart.Headers, v.StatisticMethod)
+		chart.SaleMount = append(chart.SaleMount, v.SaleAllMount)
+		chart.SaleWeight = append(chart.SaleWeight, v.SaleAllWeight)
+		chart.SaleCount = append(chart.SaleCount, v.SaleAllCount)
+		chart.AverageSaleWeight = append(chart.AverageSaleWeight, v.SaleAvgWeight)
+		v.Id = int32(i)
+	}
+
+	return &pasturePb.SaleCowReportResponse{
+		Code:    http.StatusOK,
+		Message: "ok",
+		Data: &pasturePb.SaleCowReportData{
+			Chart: chart,
+			List:  list,
+			Total: int32(len(list)),
+		},
+	}, err
+}

+ 44 - 0
module/backend/config_data_breed.go

@@ -204,3 +204,47 @@ func (s *StoreEntry) MultiFactorAnalysisMethodEnumList(isAll string) []*pastureP
 	})
 	return configOptions
 }
+
+func (s *StoreEntry) SaleCowAnalysisMethodEnumList(isAll string) []*pasturePb.ConfigOptionsList {
+	configOptions := make([]*pasturePb.ConfigOptionsList, 0)
+	configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.SaleCowAnalysisMethod_Months),
+		Label:    "月份",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.SaleCowAnalysisMethod_Dealer),
+		Label:    "经销商",
+		Disabled: true,
+	})
+	return configOptions
+}
+
+func (s *StoreEntry) NeckRingStatusEnumList(isAll string) []*pasturePb.ConfigOptionsList {
+	configOptions := make([]*pasturePb.ConfigOptionsList, 0)
+	if isAll == model.IsAllYes {
+		configOptions = append(configOptions,
+			&pasturePb.ConfigOptionsList{
+				Value:    int32(pasturePb.NeckRingStatus_Invalid),
+				Label:    "全部",
+				Disabled: true,
+			})
+	}
+	configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.NeckRingStatus_Bind),
+		Label:    "绑定",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.NeckRingStatus_Normal),
+		Label:    "正常",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.NeckRingStatus_Offline),
+		Label:    "在线",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.NeckRingStatus_Error),
+		Label:    "异常",
+		Disabled: true,
+	})
+	return configOptions
+}

+ 8 - 0
module/backend/enum_map.go

@@ -190,3 +190,11 @@ func (s *StoreEntry) MultiFactorAnalysisMethodMap() map[pasturePb.MultiFactorAna
 	res[pasturePb.MultiFactorAnalysisMethod_Breeding_Method] = "expose_estrus_type"
 	return res
 }
+
+func (s *StoreEntry) NeckRingStatusMap() map[pasturePb.NeckRingStatus_Kind]string {
+	res := make(map[pasturePb.NeckRingStatus_Kind]string)
+	for _, v := range s.NeckRingStatusEnumList("") {
+		res[pasturePb.NeckRingStatus_Kind(v.Value)] = v.Label
+	}
+	return res
+}

+ 2 - 0
module/backend/enum_options.go

@@ -188,6 +188,8 @@ func (s *StoreEntry) SystemBaseConfigOptions(ctx context.Context, optionsName, i
 		"singleFactorAnalysisMethod": s.SingleFactorAnalysisMethodEnumList,
 		"lactIntervalSymbol":         s.LactIntervalSymbolEnumList,
 		"multiFactorAnalysisMethod":  s.MultiFactorAnalysisMethodEnumList,
+		"saleCowAnalysisMethod":      s.SaleCowAnalysisMethodEnumList,
+		"neckRingStatus":             s.NeckRingStatusEnumList,
 	}
 
 	getConfigFunc, ok := getConfigFuncMap[optionsName]

+ 100 - 3
module/backend/goods.go

@@ -5,6 +5,9 @@ import (
 	"fmt"
 	"kpt-pasture/model"
 	"net/http"
+	"time"
+
+	"gorm.io/gorm"
 
 	"gitee.com/xuyiping_admin/pkg/xerr"
 
@@ -93,16 +96,110 @@ func (s *StoreEntry) MedicalEquipmentList(ctx context.Context, req *pasturePb.Se
 }
 
 func (s *StoreEntry) MedicalEquipmentCreateOrUpdate(ctx context.Context, req *pasturePb.SearchMedicalEquipmentList) error {
-	currentUser, _ := s.GetCurrentSystemUser(ctx)
+	currentUser, err := s.GetCurrentSystemUser(ctx)
+	if err != nil {
+		return xerr.Custom("登录人信息失效")
+	}
 	newDrugs := model.NewMedicalEquipment(req, currentUser)
 	if req.Id <= 0 {
-		if err := s.DB.Create(newDrugs).Error; err != nil {
+		if err = s.DB.Create(newDrugs).Error; err != nil {
 			return xerr.WithStack(err)
 		}
 	} else {
-		if err := s.DB.Where("id = ?", req.Id).Updates(newDrugs).Error; err != nil {
+		if err = s.DB.Where("id = ?", req.Id).Updates(newDrugs).Error; err != nil {
 			return xerr.WithStack(err)
 		}
 	}
 	return nil
 }
+
+func (s *StoreEntry) NeckRingLogCreateOrUpdate(ctx context.Context, req *pasturePb.NeckRingCreateRequest) error {
+	currentUser, err := s.GetCurrentSystemUser(ctx)
+	if err != nil {
+		return xerr.Custom("登录人信息失效")
+	}
+
+	if req.Items == nil || len(req.Items) == 0 {
+		return xerr.Custom("请选择要脖环数据")
+	}
+
+	newNeckRingLogList := model.NewNeckRingLogList(req.Items, currentUser)
+	cowIds := make([]int64, 0)
+	for _, v := range newNeckRingLogList {
+		cowIds = append(cowIds, v.CowId)
+	}
+
+	if err = s.DB.Transaction(func(tx *gorm.DB) error {
+		// 解绑脖环号
+		if err = tx.Model(new(model.NeckRingLog)).
+			Where("cowId IN ?", cowIds).
+			Updates(map[string]interface{}{
+				"unbind_at":      time.Now().Unix(),
+				"status":         pasturePb.NeckRingStatus_Unbind,
+				"operation_id":   currentUser.Id,
+				"operation_name": currentUser.Name,
+			}).Error; err != nil {
+			return xerr.WithStack(err)
+		}
+		// 绑定新脖环
+		if err = tx.Create(newNeckRingLogList).Error; err != nil {
+			return xerr.WithStack(err)
+		}
+
+		for _, v := range req.Items {
+			if v.CowId > 0 {
+				if err = tx.Model(new(model.Cow)).
+					Where("id = ?", v.CowId).
+					Where("admission_status = ?", pasturePb.AdmissionStatus_Admission).
+					Updates(map[string]interface{}{
+						"neck_ring_number": v.Number,
+					}).Error; err != nil {
+					return xerr.WithStack(err)
+				}
+			}
+		}
+
+		return nil
+	}); err != nil {
+		return xerr.WithStack(err)
+	}
+	return nil
+}
+
+func (s *StoreEntry) NeckRingLogList(ctx context.Context, req *pasturePb.SearchNeckRingRequest, pagination *pasturePb.PaginationModel) (*pasturePb.SearchNeckRingResponse, error) {
+	neckRingLogList := make([]*model.NeckRingLog, 0)
+	var count int64 = 0
+
+	pref := s.DB.Model(new(model.NeckRingLog)).Where("status > ?", pasturePb.NeckRingStatus_Unbind)
+	if req.Status > 0 {
+		pref.Where("status = ?", req.Status)
+	}
+
+	if req.CowId > 0 {
+		pref.Where("cow_id = ?", req.CowId)
+	}
+
+	if req.Number != "" {
+		pref.Where("number like ?", fmt.Sprintf("%s%s%s", "%", req.Number, "%"))
+	}
+
+	if err := pref.Order("id desc").
+		Count(&count).
+		Limit(int(pagination.PageSize)).
+		Offset(int(pagination.PageOffset)).
+		Find(&neckRingLogList).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	neckRingStatusMap := s.NeckRingStatusMap()
+	return &pasturePb.SearchNeckRingResponse{
+		Code:    http.StatusOK,
+		Message: "ok",
+		Data: &pasturePb.SearchNeckRingData{
+			List:     model.NeckRingLogSlice(neckRingLogList).ToPB(neckRingStatusMap),
+			Total:    int32(count),
+			PageSize: pagination.PageSize,
+			Page:     pagination.Page,
+		},
+	}, nil
+}

+ 3 - 0
module/backend/interface.go

@@ -203,6 +203,8 @@ type GoodsService interface {
 	DrugsCreateOrUpdate(ctx context.Context, req *pasturePb.SearchDrugsList) error
 	MedicalEquipmentList(ctx context.Context, req *pasturePb.SearchMedicalEquipmentRequest, pagination *pasturePb.PaginationModel) (*pasturePb.SearchMedicalEquipmentResponse, error)
 	MedicalEquipmentCreateOrUpdate(ctx context.Context, req *pasturePb.SearchMedicalEquipmentList) error
+	NeckRingLogCreateOrUpdate(ctx context.Context, req *pasturePb.NeckRingCreateRequest) error
+	NeckRingLogList(ctx context.Context, req *pasturePb.SearchNeckRingRequest, pagination *pasturePb.PaginationModel) (*pasturePb.SearchNeckRingResponse, error)
 }
 
 //go:generate mockgen -destination mock/AnalyseService.go -package kptservicemock kpt-pasture/module/backend AnalyseService
@@ -216,6 +218,7 @@ type AnalyseService interface {
 	PregnancyReport(ctx context.Context, req *pasturePb.PregnancyReportRequest, pagination *pasturePb.PaginationModel) (*pasturePb.PregnancyReportResponse, error)
 	CalvingReport(ctx context.Context, req *pasturePb.CalvingReportRequest) (*pasturePb.CalvingReportResponse, error)
 	DiseaseCureReport(ctx context.Context, req *pasturePb.DiseaseCureRateRequest) (*pasturePb.DiseaseCureRateResponse, error)
+	SaleCowReport(ctx context.Context, req *pasturePb.SaleCowReportRequest) (*pasturePb.SaleCowReportResponse, error)
 	SingleFactorInfantSurvivalRateAnalysis(ctx context.Context, req *pasturePb.SingleFactorPregnancyRateRequest) (*pasturePb.SingleFactorPregnancyRateResponse, error)
 	MultipleFactorAnalysis(ctx context.Context, req *pasturePb.MultiFactorPregnancyRateRequest) (*model.MultiFactorPregnancyRateResponse, error)
 }