| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493 | package crontabimport (	"fmt"	"kpt-pasture/model"	"kpt-pasture/util"	"sort"	"strings"	"time"	"gorm.io/gorm"	"gitee.com/xuyiping_admin/pkg/logger/zaplog"	"go.uber.org/zap")// UpdateRecognitionTime 识别时间超过40分钟未套杯牛只,识别改为未识别func (e *Entry) UpdateRecognitionTime(pastureId int64, hall *model.MilkHall) {	milkOriginalList := make([]*model.MilkOriginal, 0)	if err := e.DB.Model(new(model.MilkOriginal)).		Where("pasture_id = ?", pastureId).		Where("milk_hall_number = ?", hall.Name).		Where("milk_hall_brand = ?", hall.Brand).		Where("load = ?", 0).		Find(&milkOriginalList).Error; err != nil {		zaplog.Error("MilkHallData", zap.Any("err", err))	}	for _, v := range milkOriginalList {		t1, _ := util.TimeParseLocal(model.LayoutTime, v.AttachTime)		t2, _ := util.TimeParseLocal(model.LayoutTime, v.RecognitionTime)		diff := t1.Sub(t2)		minute := int(diff.Minutes())		if util.Substr(v.RecognitionTime, -1, 8) != "00:00:00" && minute > 40 {			if err := e.DB.Model(new(model.MilkOriginal)).				Where("id = ?", v.Id).				Updates(map[string]interface{}{					"cow_id":           0,					"ele_ear_number":   "",					"recognition_time": fmt.Sprintf("%s 00:00:00", util.Substr(v.RecognitionTime, 0, 10)),				}).Error; err != nil {				zaplog.Error("MilkHallData", zap.Any("err", err))			}		}	}}// UpdateRepeatCupSet1 更新重复套杯1, 识别时间相同,且不为0为重复套杯func (e *Entry) UpdateRepeatCupSet1(milkOriginalList []*model.MilkOriginal) {	if len(milkOriginalList) == 0 {		return	}	milkOriginalMap := make(map[string][]*model.MilkOriginal)	for _, v := range milkOriginalList {		if strings.HasSuffix(v.RecognitionTime, "00:00:00") {			continue		}		key := fmt.Sprintf("%s_%d_%d_%s", v.MilkDate, v.Shifts, v.DetachedAddress, v.RecognitionTime)		milkOriginalMap[key] = append(milkOriginalMap[key], v)	}	for _, originalList := range milkOriginalMap {		if len(originalList) >= 2 {			// 按照Id升序排序(保留第一条)			sort.Slice(originalList, func(i, j int) bool {				return originalList[i].Id < originalList[j].Id			})			for i, v := range originalList {				if i == 0 {					continue				}				if err := e.DB.Model(new(model.MilkOriginal)).					Select("").Where("id = ?", v.Id).					Update("nattach", 2).Error; err != nil {					zaplog.Error("UpdateRepeatCupSet1", zap.Any("err", err))				}			}		}	}}// UpdateMilkOriginCowInfo 更新牛只信息func (e *Entry) UpdateMilkOriginCowInfo(milkOriginalList []*model.MilkOriginal, hall *model.MilkHall) {	milkHallMap := make(map[string][]*model.MilkOriginal)	for _, v := range milkOriginalList {		key := fmt.Sprintf("%s", v.MilkHallNumber)		milkHallMap[key] = append(milkHallMap[key], v)	}	dataList, ok := milkHallMap[hall.Name]	if !ok {		return	}	switch hall.IsExtraUpdate {	case model.IsExtra0:	case model.IsExtra1, model.IsExtra3:		for _, d := range dataList {			if d.EarNumber == "" {				continue			}			cowInfo, err := e.GetCowByEarNumber(d.PastureId, d.EarNumber)			if err != nil {				zaplog.Error("UpdateMilkOriginCowInfo", zap.Any("err", err), zap.Any("data", d))				continue			}			// 更新牛只信息			d.UpdateCowInfo(cowInfo)			if err = e.DB.Model(new(model.MilkOriginal)).				Select("cow_id", "pen_id", "pen_name").				Where("id = ?", d.Id).Updates(d).Error; err != nil {				zaplog.Error("UpdateMilkOriginCowInfo", zap.Any("err", err), zap.Any("data", d))			}		}	case model.IsExtra2:	default:	}}func (e *Entry) UpdateMilkOriginalInitialTimesAndAttachAdjustTime(shifts []int32, milkOriginalList []*model.MilkOriginal) {	for _, shift := range shifts {		shiftMinDetachTimes := ""		// 按脱杯地址分组处理		addressMap := make(map[int64][]*model.MilkOriginal)		for _, m := range milkOriginalList {			if m.Shifts != shift || m.DetachedTime == "" {				continue			}			if shiftMinDetachTimes == "" {				shiftMinDetachTimes = m.DetachedTime			} else {				t1, _ := util.TimeParseLocal(model.LayoutTime, m.DetachedTime)				t2, _ := util.TimeParseLocal(model.LayoutTime, shiftMinDetachTimes)				if t2.Before(t1) {					shiftMinDetachTimes = m.DetachedTime				}			}			addressMap[m.DetachedAddress] = append(addressMap[m.DetachedAddress], m)		}		if shiftMinDetachTimes == "" {			continue		}		bt, _ := util.TimeParseLocal(model.LayoutTime, shiftMinDetachTimes)		b5 := bt.Add(-5*time.Minute).Format(model.LayoutHour) + "00:00"		for _, list := range addressMap {			// 对当前地址的记录按时间排序			sort.Slice(list, func(i, j int) bool {				if list[i].MilkDate != list[j].MilkDate {					return list[i].MilkDate < list[j].MilkDate				}				if list[i].Shifts != list[j].Shifts {					return list[i].Shifts < list[j].Shifts				}				if list[i].DetachedAddress != list[j].DetachedAddress {					return list[i].DetachedAddress < list[j].DetachedAddress				}				return list[i].Id < list[j].Id			})			// 初始化变量,模拟SQL中的@address和@det			var lastAddress int64 = 0			var lastDetachTime string = "2001-01-01 06:00:00" // 默认初始值			// 批量更新参数			var updateParams []struct {				ID           int64				InitialTimes string				AttachAdjust string			}			for _, m := range list {				var initialTimeStr string				var attachAdjust string				// 如果当前记录的脱杯地址与上一条不同,则使用基准时间b5				if m.DetachedAddress != lastAddress {					initialTimeStr = b5				} else {					// 否则使用上一条记录的脱杯时间					initialTimeStr = lastDetachTime				}				// 更新最后记录的地址和时间				lastAddress = m.DetachedAddress				lastDetachTime = m.DetachedTime				// 只有当initialTime不为空且与原有值不同时才需要更新				if initialTimeStr != "" {					initialTime, _ := util.TimeParseLocal(model.LayoutTime, initialTimeStr)					attachTime, _ := util.TimeParseLocal(model.LayoutTime, m.AttachTime)					detachTime, _ := util.TimeParseLocal(model.LayoutTime, m.DetachedTime)					// 条件1:attachtimes以'00:00:00'结尾或attachtimes <= initialtimes					if strings.HasSuffix(m.AttachTime, "00:00:00") || attachTime.Before(initialTime) || attachTime.Equal(initialTime) {						// 计算 detachtimes - (1.5 + duration)*60 秒						adjustTime1 := detachTime.Add(-time.Duration((90 + m.Duration*60)) * time.Second)						// 取三者中的最大值						maxTime := util.FindMaxTime(attachTime, initialTime, adjustTime1)						attachAdjust = maxTime.Format(model.LayoutTime)					} else {						// 计算 detachtimes - duration*60 秒						adjustTime2 := detachTime.Add(-time.Duration(m.Duration*60) * time.Second)						// 取 attachtimes 和 adjustTime2 中的较小值						minTime := util.FindMinTime(attachTime, adjustTime2)						// 再与 initialtimes 取较大值						maxTime := util.FindMaxTime(minTime, initialTime)						attachAdjust = maxTime.Format(model.LayoutTime)					}					// 记录需要更新的字段					updateParams = append(updateParams, struct {						ID           int64						InitialTimes string						AttachAdjust string					}{						ID:           m.Id,						InitialTimes: initialTimeStr,						AttachAdjust: attachAdjust,					})					/*if err := e.DB.Model(new(model.MilkOriginal)).						Select("initial_time").						Where("id = ?", m.Id).						Update("initial_time", initialTime).Error; err != nil {						zaplog.Error("UpdateMilkOriginalInitialTimesAndAttachAdjustTime", zap.Any("err", err))					}*/				}			}			if len(updateParams) > 0 {				// 批量更新数据库				if err := e.DB.Transaction(func(tx *gorm.DB) error {					for _, param := range updateParams {						updates := map[string]interface{}{							"initial_times":      param.InitialTimes,							"attach_adjust_time": param.AttachAdjust,						}						if err := tx.Model(new(model.MilkOriginal)).							Select("initial_time", "attach_adjust_time").							Where("id = ? ", param.ID).							Updates(updates).Error; err != nil {							return err						}					}					return nil				}); err != nil {					zaplog.Error("UpdateMilkOriginalInitialTimesAndAttachAdjustTime", zap.Any("err", err))				}			}		}	}}// UpdateRepeatCupSet2  非标准重复套杯func (e *Entry) UpdateRepeatCupSet2(milkOriginalList []*model.MilkOriginal) {	for _, v := range milkOriginalList {		if v.AttachTime == "" || v.InitialTime == "" {			continue		}		nattchTime, _ := util.TimeParseLocal(model.LayoutTime, v.AttachTime)		initialTime, _ := util.TimeParseLocal(model.LayoutTime, v.InitialTime)		if util.Substr(v.InitialTime, -1, 5) != "00:00" && v.Nattach == 0 && nattchTime.Sub(initialTime).Minutes() <= 1 {			if err := e.DB.Model(new(model.MilkOriginal)).				Select("nattach").				Where("id = ?", v.Id).				Update("nattach", 2).Error; err != nil {				zaplog.Error("UpdateRepeatCupSet2", zap.Any("err", err))			}		}	}}// UpdateMilkNattach 非标准重复套杯func (e *Entry) UpdateMilkNattach(pastureId int64, milkClassConfig *MilkClassConfig, hall *model.MilkHall) {	milkOriginalList := make([]*model.MilkOriginal, 0)	if err := e.DB.Model(new(model.MilkOriginal)).		Where("pasture_id = ?", pastureId).		Where("id BETWEEN ? AND ?", milkClassConfig.OldUpdateMaxId+1, milkClassConfig.CurrentMaxId).		Find(&milkOriginalList).Error; err != nil {		zaplog.Error("DeleteRepeatMilkData", zap.Any("pastureId", pastureId), zap.Any("err", err))		return	}	for _, v := range milkOriginalList {		if v.InitialTime == "" || v.AttachTime == "" {			continue		}		attachTime, _ := util.TimeParseLocal(model.LayoutTime, v.AttachTime)		initialTime, _ := util.TimeParseLocal(model.LayoutTime, v.InitialTime)		initialTimePlus1Min := initialTime.Add(time.Minute)		if hall.Brand == v.MilkHallBrand && hall.Name == v.MilkHallNumber && v.Nattach == 0 &&			!strings.HasSuffix(v.InitialTime, "00:00") && (attachTime.Before(initialTimePlus1Min) || attachTime.Equal(initialTimePlus1Min)) {			if err := e.DB.Model(new(model.MilkOriginal)).				Select("nattach").				Where("id = ?", v.Id).				Update("nattach", 2).Error; err != nil {				zaplog.Error("UpdateMilkNattach", zap.Any("err", err))			}		}	}}// UpdateMilkNoCowId 清理无牛号牛只:重复超过2圈牛号,套杯时间间隔大于17分钟func (e *Entry) UpdateMilkNoCowId(pastureId int64, milkClassConfig *MilkClassConfig, hall *model.MilkHall) {	//  SELECT 	m2.wid	//	FROM (SELECT m0.milkdate, m0.shifts, m0.detacher_address, m0.cow_id, MIN(m0.wid) wid, COUNT(0) nb, MIN(m0.attachtimes) attachtimes	//	FROM milkweight m0 WHERE m0.wid BETWEEN xdminwid AND xdmaxwid AND m0.milkdate=xcurdate AND m0.cow_id>0 AND m0.station=xvarName	//	GROUP BY m0.shifts, m0.detacher_address, m0.cow_id HAVING nb>1) m1	//	JOIN milkweight m2 ON m1.milkdate=m2.milkdate AND m1.shifts=m2.shifts AND m2.station=xvarName AND m1.detacher_address=m2.detacher_address	//	AND m1.cow_id=m2.cow_id AND m1.wid<m2.wid WHERE m2.wid BETWEEN xdminwid AND xdmaxwid AND m2.milkdate=xcurdate AND m2.cow_id>0	//	AND (m1.attachtimes + INTERVAL 17 MINUTE) < m2.attachtimes ;	minMilkOriginalRecordsList := make([]*model.MinMilkOriginalRecords, 0)	if err := e.DB.Model(new(model.MilkOriginal)).		Select("MIN(id) AS min_id, COUNT(0) AS count, cow_id, detached_address, milk_date, MIN(attach_time) AS min_attach_time").		Where("pasture_id = ?", pastureId).		Where("id BETWEEN ? AND ?", milkClassConfig.OldUpdateMaxId+1, milkClassConfig.CurrentMaxId).		Where("cow_id > ?", 0).		Where("milk_hall_number = ?", hall.Name).		Group("shifts, detached_address, cow_id").		Having("count > ? AND min_attach_time != ?", 1, "").		Find(&minMilkOriginalRecordsList).Error; err != nil {		zaplog.Error("UpdateMilkNoCowId", zap.Any("err", err))		return	}	// 2. 收集需要更新的 id 列表	idsToUpdate := make([]int64, 0)	for _, v := range minMilkOriginalRecordsList {		var laterRecords []struct {			Id int64		}		attachTime, _ := util.TimeParseLocal(model.LayoutTime, v.MinAttachTime)		attachTimeAdd17 := attachTime.Add(time.Minute * 17).Format(model.LayoutTime)		if err := e.DB.Model(new(model.MilkOriginal)).			Select("id").			Where("pasture_id = ?", pastureId).			Where("id BETWEEN ? AND ?", milkClassConfig.OldUpdateMaxId+1, milkClassConfig.CurrentMaxId).			Where("cow_id = ?", v.CowId).			Where("detached_address = ?", v.DetachedAddress).			Where("milk_date = ?", v.MilkDate).			Where("attach_time > ?", attachTimeAdd17).Find(&laterRecords).Error; err != nil {			zaplog.Error("UpdateMilkNoCowId", zap.Any("err", err))			continue		}		if len(laterRecords) > 0 {			idsToUpdate = append(idsToUpdate, v.MinId)		}	}	if len(idsToUpdate) > 0 {		if err := e.DB.Model(new(model.MilkOriginal)).			Where("id IN ?", idsToUpdate).			Updates(map[string]interface{}{				"cow_id":     0,				"ear_number": "",				"pen_id":     0,				"pen_name":   "",			}).Error; err != nil {			zaplog.Error("UpdateMilkNoCowId", zap.Any("err", err))		}	}}// UpdateMilkLoad 设置批次 圈数func (e *Entry) UpdateMilkLoad(pastureId int64, milkClassConfig *MilkClassConfig, hall *model.MilkHall) {	var (		xAddress = int32(999)		//xShift          = 6		xLoad           = int32(0)		recognitionTime = time.Date(2001, 1, 1, 1, 1, 1, 0, time.Local)	)	baseRecords := make([]*model.BaseRecords, 0)	if err := e.DB.Model(new(model.MilkOriginal)).		Select("id,attach_adjust,detached_address,shifts,ear_number,milk_date,recognition_time").		Where("pasture_id = ?", pastureId).		Where("id BETWEEN ? AND ?", milkClassConfig.OldUpdateMaxId+1, milkClassConfig.CurrentMaxId).		Where("nattach = ?", 0).Where("milk_hall_number = ?", hall.Name).		Order("attach_adjust,detached_address").		Limit(99).Find(&baseRecords).Error; err != nil {		zaplog.Error("UpdateMilkLoad", zap.Any("err", err))		return	}	// 3. 处理每条记录并计算 load 值	recordsToUpdate := make([]*model.UpdateLoadRecord, 0)	for _, record := range baseRecords {		if record.RecognitionTime != "" && record.AttachAdjust != "" {			r, _ := util.TimeParseLocal(model.LayoutTime, record.RecognitionTime)			a, _ := util.TimeParseLocal(model.LayoutTime, record.AttachAdjust)			diff := int32(a.Sub(r).Minutes())			if !(diff >= 0 && diff <= 15 && !strings.HasSuffix(record.RecognitionTime, "00:00:00")) {				record.RecognitionTime = record.AttachAdjust			}		}		r, _ := util.TimeParseLocal(model.LayoutTime, record.RecognitionTime)		/*var maxLoad int32		if (record.DetachedAddress < xAddress-27 && r.After(recognitionTime.Add(-5*time.Minute))) || record.Shifts > xShift || r.After(recognitionTime.Add(6*time.Minute)) {			xLoad++			maxLoad = xLoad		} else {			maxLoad = xLoad		}*/		// 计算 currentLoad		var currentLoad int32		if (xAddress+60-record.DetachedAddress <= 27) &&			r.Before(recognitionTime.Add(3*time.Minute)) {			currentLoad = maxInt(xLoad-1, 1)		} else {			currentLoad = xLoad		}		// 更新地址		if (xAddress+60-record.DetachedAddress <= 27) &&			r.Before(recognitionTime.Add(3*time.Minute)) {			// 保持原地址		} else {			xAddress = record.DetachedAddress		}		// 更新shift和时间		//xShift = record.Shifts		if r.After(recognitionTime) {			recognitionTime = r		}		// 记录需要更新的数据		recordsToUpdate = append(recordsToUpdate, &model.UpdateLoadRecord{			Id:   record.Id,			Load: currentLoad,		})	}	if len(recordsToUpdate) > 0 {		batchSize := 100 // 每批更新100条		for i := 0; i < len(recordsToUpdate); i += batchSize {			end := i + batchSize			if end > len(recordsToUpdate) {				end = len(recordsToUpdate)			}			batch := recordsToUpdate[i:end]			if err := e.DB.Model(new(model.MilkOriginal)).				Where("id IN (?)", getWIDsFromUpdateRecords(batch)).				Updates(map[string]interface{}{					"load": gorm.Expr("CASE id " + buildCaseWhen(batch) + " END"),				}).Error; err != nil {				zaplog.Error("UpdateMilkLoad", zap.Any("err", err))				continue			}		}	}}// 辅助函数:从更新记录中提取WID列表func getWIDsFromUpdateRecords(records []*model.UpdateLoadRecord) []int64 {	ids := make([]int64, len(records))	for i, r := range records {		ids[i] = r.Id	}	return ids}// 辅助函数:构建CASE WHEN语句func buildCaseWhen(records []*model.UpdateLoadRecord) string {	var builder strings.Builder	for _, r := range records {		builder.WriteString(fmt.Sprintf("WHEN %d THEN %d ", r.Id, r.Load))	}	builder.WriteString("ELSE load")	return builder.String()}// 辅助函数:返回两个整数中的最大值func maxInt(a, b int32) int32 {	if a > b {		return a	}	return b}
 |