package backend import ( "bytes" "context" "encoding/json" "errors" "fmt" "io" "kpt-tmr-group/model" "kpt-tmr-group/pkg/logger/zaplog" "kpt-tmr-group/pkg/xerr" operationPb "kpt-tmr-group/proto/go/backend/operation" "net/http" "strconv" "sync" "time" "go.uber.org/multierr" "github.com/xuri/excelize/v2" "go.uber.org/zap" "gorm.io/gorm" ) const EncodeNumberPrefix = "encode_number" var PastureDataLogType = map[string]int32{ "FeedFormula_Distribute": 1, "FeedFormula_IsModify": 2, } // CreateFeedFormula 添加数据 func (s *StoreEntry) CreateFeedFormula(ctx context.Context, req *operationPb.AddFeedFormulaRequest) error { forage := model.NewFeedFormula(req) if err := s.DB.Create(forage).Error; err != nil { return xerr.WithStack(err) } return nil } // EditFeedFormula 编辑数据 func (s *StoreEntry) EditFeedFormula(ctx context.Context, req *operationPb.AddFeedFormulaRequest) error { forage := model.FeedFormula{Id: int64(req.Id)} if err := s.DB.Where("is_delete = ?", operationPb.IsShow_OK).First(&forage).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return xerr.Custom("该数据不存在") } return xerr.WithStack(err) } updateData := &model.FeedFormula{ Name: req.Name, Colour: req.Colour, CattleCategoryId: req.CattleCategoryId, CattleCategoryName: req.CattleCategoryName, FormulaTypeId: req.FormulaTypeId, FormulaTypeName: req.FormulaTypeName, DataSourceId: req.DataSourceId, DataSourceName: req.DataSourceName, Remarks: req.Remarks, IsShow: req.IsShow, } if err := s.DB.Model(new(model.FeedFormula)). Omit("is_show", "is_delete", "encode_number", "formula_type_id", "formula_type_name", "data_source", "version", "is_modify"). Where("id = ?", req.Id). Updates(updateData).Error; err != nil { return xerr.WithStack(err) } return nil } // SearchFeedFormulaList 查询数据列表 func (s *StoreEntry) SearchFeedFormulaList(ctx context.Context, req *operationPb.SearchFeedFormulaRequest) (*operationPb.SearchFeedFormulaListResponse, error) { feedFormula := make([]*model.FeedFormula, 0) var count int64 = 0 pref := s.DB.Model(new(model.FeedFormula)).Where("is_delete = ?", operationPb.IsShow_OK) if req.Name != "" { pref.Where("name like ?", fmt.Sprintf("%s%s%s", "%", req.Name, "%")) } if req.CattleCategoryId > 0 { pref.Where("cattle_category_id = ?", req.CattleCategoryId) } if req.FormulaTypeId > 0 { pref.Where("formula_type_id = ?", req.FormulaTypeId) } if req.IsShow > 0 { pref.Where("is_show = ?", req.IsShow) } if req.DataSource > 0 { pref.Where("data_source = ?", req.DataSource) } if req.Remarks != "" { pref.Where("remarks = ?", req.Remarks) } if err := pref.Order("id desc").Count(&count).Limit(int(req.Pagination.PageSize)).Offset(int(req.Pagination.PageOffset)). Find(&feedFormula).Error; err != nil { return nil, xerr.WithStack(err) } return &operationPb.SearchFeedFormulaListResponse{ Code: http.StatusOK, Msg: "ok", Data: &operationPb.SearchFeedFormulaListData{ Page: req.Pagination.Page, PageSize: req.Pagination.PageSize, Total: int32(count), List: model.FeedFormulaSlice(feedFormula).ToPB(), }, }, nil } // IsShowFeedFormula 是否启用和是否可修改 func (s *StoreEntry) IsShowFeedFormula(ctx context.Context, req *operationPb.IsShowModifyFeedFormula) error { feedFormula := &model.FeedFormula{Id: int64(req.FeedFormulaId)} if err := s.DB.First(feedFormula).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return xerr.Custom("该数据不存在") } return xerr.WithStack(err) } if req.EditType == 1 { if err := s.DB.Model(new(model.FeedFormula)).Where("id = ?", req.FeedFormulaId).Update("is_show", req.IsShow).Error; err != nil { return xerr.WithStack(err) } } if req.EditType == 2 { if err := s.DB.Model(new(model.FeedFormula)).Where("id = ?", req.FeedFormulaId).Update("is_modify", req.IsShow).Error; err != nil { return xerr.WithStack(err) } else { s.PastureFeedFormulaIsModify(ctx, req.FeedFormulaId, req.IsShow) } } return nil } // DeleteFeedFormula 是否删除 func (s *StoreEntry) DeleteFeedFormula(ctx context.Context, feedFormulaId int64) error { feedFormula := &model.FeedFormula{Id: feedFormulaId} if err := s.DB.First(feedFormula).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return xerr.Custom("该数据不存在") } return xerr.WithStack(err) } if err := s.DB.Model(new(model.FeedFormula)).Where("id = ?", feedFormula.Id).Update("is_delete", operationPb.IsShow_NO).Error; err != nil { return xerr.WithStack(err) } return nil } // ExcelImportFeedFormula 导入excel func (s *StoreEntry) ExcelImportFeedFormula(ctx context.Context, req io.Reader) error { xlsx, err := excelize.OpenReader(req) if err != nil { return xerr.WithStack(err) } defer xlsx.Close() rows, err := xlsx.GetRows(xlsx.GetSheetName(xlsx.GetActiveSheetIndex())) if err != nil { return xerr.WithStack(err) } if len(rows) > 10000 { rows = rows[:10000] } feedFormulaList := make([]*model.FeedFormula, 0) for i, row := range rows { if i == 0 { continue } var ( name, encodeNumber, cattleCategoryName, formulaTypeName, dataSourceName, remarks, isShowStr string isShow operationPb.IsShow_Kind ) for k, v := range row { if k == 0 { name = v } if k == 1 { encodeNumber = v } if k == 2 { cattleCategoryName = v } if k == 3 { formulaTypeName = v } if k == 4 { dataSourceName = v } if k == 5 { remarks = v } if k == 6 { isShowStr = v } } if isShowStr == "是" { isShow = operationPb.IsShow_OK } else { isShow = operationPb.IsShow_NO } feedFormulaItem := &model.FeedFormula{ Name: name, EncodeNumber: encodeNumber, CattleCategoryName: cattleCategoryName, FormulaTypeName: formulaTypeName, Remarks: remarks, IsShow: isShow, IsDelete: operationPb.IsShow_OK, DataSourceId: operationPb.DataSource_EXCEL_IMPORT, DataSourceName: dataSourceName, } feedFormulaList = append(feedFormulaList, feedFormulaItem) } if len(feedFormulaList) > 0 { if err = s.DB.Create(feedFormulaList).Error; err != nil { return xerr.WithStack(err) } } return nil } // ExcelExportFeedFormula 流式导出excel func (s *StoreEntry) ExcelExportFeedFormula(ctx context.Context, req *operationPb.SearchFeedFormulaRequest) (*bytes.Buffer, error) { res, err := s.SearchFeedFormulaList(ctx, req) if err != nil { return nil, xerr.WithStack(err) } if len(res.Data.List) <= 0 { return nil, xerr.Custom("数据为空") } file := excelize.NewFile() defer file.Close() streamWriter, err := file.NewStreamWriter("Sheet1") if err != nil { return nil, xerr.WithStack(err) } titles := []interface{}{"配方名称", "配方编码", "畜牧类别", "配方类别", "来源", "备注", "是否启用", "饲料组", "饲料名称", "重量(kg)", "搅拌延迟(min)", "是否锁定牛头数比例", "顺序"} if err = streamWriter.SetRow("A1", titles); err != nil { return nil, xerr.WithStack(err) } for i, item := range res.Data.List { cell, err := excelize.CoordinatesToCellName(1, i+2) if err != nil { zaplog.Error("excelize.CoordinatesToCellName", zap.Any("Err", err)) continue } row := make([]interface{}, 0) row = append(row, item.Name, item.EncodeNumber, item.CattleCategoryName, item.FormulaTypeName, item.DataSourceName, item.Remarks, item.IsShow) if err = streamWriter.SetRow(cell, row); err != nil { return nil, xerr.WithStack(err) } } if err = streamWriter.Flush(); err != nil { return nil, xerr.WithStack(err) } return file.WriteToBuffer() } // ExcelTemplateFeedFormula 导出模板 func (s *StoreEntry) ExcelTemplateFeedFormula(ctx context.Context) (*bytes.Buffer, error) { file := excelize.NewFile() defer file.Close() streamWriter, err := file.NewStreamWriter("Sheet1") if err != nil { return nil, xerr.WithStack(err) } titles := []interface{}{"配方名称", "配方编码", "畜牧类别", "配方类别", "来源", "备注", "是否启用", "饲料组", "饲料名称", "重量(kg)", "搅拌延迟(min)", "是否锁定牛头数比例", "顺序"} if err = streamWriter.SetRow("A1", titles); err != nil { return nil, xerr.WithStack(err) } if err = streamWriter.Flush(); err != nil { return nil, xerr.WithStack(err) } return file.WriteToBuffer() } // EncodeNumber 配方编码 func (s *StoreEntry) EncodeNumber(ctx context.Context) string { currTime := time.Now().Format(model.LayoutDate) prefix := fmt.Sprintf("%s_%s", EncodeNumberPrefix, currTime) data := &model.UniqueData{} if err := s.DB.Order("id desc").Where("prefix = ?", prefix).First(data).Error; err != nil { if !errors.Is(err, gorm.ErrRecordNotFound) { return "" } ud, _ := strconv.Atoi(currTime) result := ud*100 + 1 newData := &model.UniqueData{ Prefix: prefix, Data: int64(result), } if err = s.DB.Create(newData).Error; err != nil { zaplog.Error("EncodeNumber Create", zap.Any("data", newData), zap.Any("Err", err)) return "" } return fmt.Sprintf("%d", newData.Data) } data.Data += 1 if err := s.DB.Model(new(model.UniqueData)).Where("prefix = ?", prefix).Update("data", data.Data).Error; err != nil { return "" } else { return fmt.Sprintf("%d", data.Data) } } // DistributeFeedFormula 配方下发牧场 func (s *StoreEntry) DistributeFeedFormula(ctx context.Context, req *operationPb.DistributeFeedFormulaRequest) error { distributeData, err := s.checkoutDistributeData(ctx, req) if err != nil { return xerr.WithStack(err) } if len(distributeData.PastureList) <= 0 { return nil } wg := sync.WaitGroup{} wg.Add(len(distributeData.PastureList)) var muError error for _, pasture := range distributeData.PastureList { go func(p *model.GroupPasture) { // 过滤已下发的 body := make([]*model.FeedFormula, 0) for _, v := range distributeData.FeedFormulaList { if ok := s.checkoutDistributeLog(ctx, p.Id, v.Id); !ok { body = append(body, v) } } if len(body) <= 0 { return } request := &model.DistributeFeedFormulaRequest{ PastureId: p.Id, Body: body, } response := &model.PastureResponse{} defer func() { if response.Code == http.StatusOK { s.DB.Create(model.NewFeedFormulaDistributeLogList(distributeData.FeedFormulaList, p.Id, p.Name, operationPb.IsShow_OK)) } else { muError = multierr.Append(muError, xerr.Custom(response.Msg)) } wg.Done() }() if _, err = s.PastureHttpClient(ctx, model.FeedFormulaDistributeUrl, p.Id, request, response); err != nil { muError = multierr.Append(muError, err) zaplog.Error("DistributeFeedFormula", zap.Any("pasture", p), zap.Any("body", distributeData.FeedFormulaList), zap.Any("err", err), zap.Any("response", response)) b, _ := json.Marshal(request) res, _ := json.Marshal(response) pastureDataLog := model.NewPastureDataLog(p.Id, PastureDataLogType["FeedFormula_Distribute"], model.FeedFormulaDistributeUrl, string(b), string(res)) s.DB.Create(pastureDataLog) } }(pasture) } wg.Wait() return muError } // FeedFormulaUsage 配方使用概况 func (s *StoreEntry) FeedFormulaUsage(ctx context.Context, req *operationPb.FeedFormulaUsageRequest) error { feedFormulaDistributeLogList := make([]*model.FeedFormulaDistributeLog, 0) if err := s.DB.Model(new(model.FeedFormulaDistributeLog)). Where("feed_formula_id = ?", req.FeedFormulaId). Where("is_show = ?", operationPb.IsShow_OK). Find(&feedFormulaDistributeLogList).Error; err != nil { return xerr.WithStack(err) } wg := sync.WaitGroup{} wg.Add(len(feedFormulaDistributeLogList)) for _, list := range feedFormulaDistributeLogList { go func(l *model.FeedFormulaDistributeLog) { defer wg.Done() }(list) } wg.Wait() return nil } func (s *StoreEntry) PastureFeedFormulaIsModify(ctx context.Context, feedFormulaId int32, isModify operationPb.IsShow_Kind) { feedFormulaDistributeLogList := make([]*model.FeedFormulaDistributeLog, 0) if err := s.DB.Where("is_show = ?", operationPb.IsShow_OK). Where("feed_formula_id = ?", feedFormulaId). Group("pasture_id").Find(&feedFormulaDistributeLogList).Error; err != nil { zaplog.Error("PastureFeedFormulaIsModify", zap.Any("err", err), zap.Any("feed_formula_id", feedFormulaId)) return } for _, v := range feedFormulaDistributeLogList { response := &model.PastureResponse{} request := &model.FeedFormulaIsModifyRequest{ PastureId: v.PastureId, FeedFormulaId: v.FeedFormulaId, IsModify: int32(isModify), } if _, err := s.PastureHttpClient(ctx, model.FeedFormulaIsModifyUrl, v.Id, request, response); err != nil { zaplog.Error("PastureFeedFormulaIsModify", zap.Any("request", request), zap.Any("err", err), zap.Any("response", response)) b, _ := json.Marshal(request) res, _ := json.Marshal(response) pastureDataLog := model.NewPastureDataLog(v.PastureId, PastureDataLogType["FeedFormula_IsModify"], model.FeedFormulaIsModifyUrl, string(b), string(res)) s.DB.Create(pastureDataLog) } } } func (s *StoreEntry) checkoutDistributeData(ctx context.Context, req *operationPb.DistributeFeedFormulaRequest) (*model.DistributeData, error) { result := &model.DistributeData{ PastureList: make([]*model.GroupPasture, 0), FeedFormulaList: make([]*model.FeedFormula, 0), } if err := s.DB.Where("id IN ?", req.PastureIds).Where("is_delete = ?", operationPb.IsShow_OK).Find(&result.PastureList).Error; err != nil { return result, xerr.WithStack(err) } if err := s.DB.Where("id IN ?", req.FeedFormulaIds).Find(&result.FeedFormulaList).Error; err != nil { return result, xerr.WithStack(err) } if len(result.PastureList) <= 0 || len(result.FeedFormulaList) <= 0 { return result, xerr.Customf("数据错误") } return result, nil } func (s *StoreEntry) checkoutDistributeLog(ctx context.Context, pastureId, feedFormulaId int64) bool { res := &model.FeedFormulaDistributeLog{} if err := s.DB.Model(new(model.FeedFormulaDistributeLog)).Where("feed_formula_id = ?", feedFormulaId). Where("pasture_id = ?", pastureId).Where("is_show = ?", operationPb.IsShow_OK).First(res).Error; err != nil { return false } if res.IsShow == operationPb.IsShow_OK { return true } return false }