package reservation import ( "context" "database/sql" "errors" "fmt" "lims_adapter/common" meeting2 "lims_adapter/dao/meeting" "lims_adapter/model" meeting3 "lims_adapter/model/meeting" "lims_adapter/service" "lims_adapter/service/meeting" "lims_adapter/service/srv" "strconv" "strings" "time" "dashoo.cn/common_definition/admin/user_def" "dashoo.cn/micro_libary/micro_srv" "dashoo.cn/micro_libary/myerrors" "dashoo.cn/micro_libary/request" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/util/gconv" ) // Service ReservationService 预约 type Service struct { Dao *meeting2.MeetingReservationDao Tenant string } // NewSrv 服务初始化 func NewSrv(tenant string) Service { return Service{Dao: meeting2.NewMeetingReservationDao(tenant), Tenant: tenant} } // List 预约列表 func (s Service) List(req model.ListReq) ([]meeting3.List, int, error) { entityModel := s.Dao.M if req.Entity != nil { entity := new(meeting3.ReservationReq) gconv.Struct(req.Entity, entity) tableSql := s.Dao.Table + "." if entity.Title != "" { entityModel = entityModel.WhereLike(tableSql+s.Dao.Columns.Title, "%"+entity.Title+"%") } if entity.EntityId != 0 { entityModel = entityModel.Where(tableSql+s.Dao.Columns.EntityId, entity.EntityId) } if entity.UserName != "" { entityModel = entityModel.WhereLike(tableSql+s.Dao.Columns.UserName, "%"+entity.UserName+"%") } // 预约状态(1:预定 2:取消) if entity.Status != 0 { entityModel = entityModel.Where(tableSql+s.Dao.Columns.Status, entity.Status) } // 预约时间 if len(entity.TimeSplit) == 2 { entityModel = entityModel.WhereBetween(tableSql+s.Dao.Columns.StartTime, entity.TimeSplit[0], entity.TimeSplit[1]) } } total, err := entityModel.Count() if err != nil { return nil, 0, err } if total == 0 { return nil, 0, nil } if req.Value != "" { entityModel = entityModel.Order(s.Dao.Table+"."+common.Snake2Orm(meeting3.MeetingReservation{}, req.Value), req.Type) } else { entityModel = entityModel.Order(s.Dao.Table+"."+s.Dao.Columns.CreatedAt, "DESC") } res, err := entityModel.Page(req.Current, req.Size). Fields("meeting_reservation.*", "meeting.Name", "base_user.DepartmentName"). LeftJoin("meeting", "meeting.Id = meeting_reservation.EntityId"). LeftJoin("base_user", "base_user.Id = meeting_reservation.UserId"). FindAll() if err != nil { return nil, 0, err } if res.IsEmpty() { return nil, 0, nil } list := make([]meeting3.List, 0) res.Structs(&list) return list, total, nil } // Add 添加预约 func (s Service) Add(userInfo request.UserInfo, req meeting3.ReservationReq) error { entity := new(meeting3.MeetingReservation) err := s.check(req) if err != nil { return err } gconv.Struct(req, entity) service.SetCreate(&userInfo, entity) entity.UserName = userInfo.RealName entity.UserId = int(userInfo.Id) entity.DepartmentId = gconv.Int(userInfo.DeptId) entity.RealEndTimeNode = req.EndTime res, err := s.Dao.M.Insert(entity) if err != nil { return err } id, _ := res.LastInsertId() // 延迟消息 go AutoProcess(int(id), s.Tenant, req.EndTime.Sub(gtime.Now())) return nil } const PermissionCancelAnyMeetingReservation = "meeting:cancel:any" // Cancel 取消预约 预约状态(1:预定 2:取消) func (s Service) Cancel(userInfo request.UserInfo, id int) error { // 判断当前用户是否是预约人 entity, err := s.Dao.WherePri(id).FindOne() if err != nil { return err } if entity == nil { return errors.New("未找到当前预约信息,请重试") } allow, err := common.Allow(s.Tenant, userInfo.Uuid, PermissionCancelAnyMeetingReservation) if err != nil { return err } if allow { updatedMap := service.SetUpdatedMap(&userInfo) updatedMap["Status"] = 2 _, err = s.Dao.M.Data(updatedMap).WherePri(id).Update() if err != nil { return err } return nil } if int32(entity.UserId) != userInfo.Id { return errors.New("当前预约只能由预约人取消") } if entity.StartTime.Sub(gtime.Now()) <= 0 { return errors.New("预约开始不允许取消") } updatedMap := service.SetUpdatedMap(&userInfo) updatedMap["Status"] = 2 _, err = s.Dao.M.Data(updatedMap).WherePri(id).Update() if err != nil { return err } return nil } // OverviewList 预约概况 func (s Service) OverviewList(req meeting3.OverviewReq) (g.Map, error) { dates := getCurrentWeekDay(req.Date) meetingList, err := meeting.NewSrv(s.Tenant).ShortList() if err != nil { return nil, err } reservationList, err := s.shortList(req.Date) if err != nil { return nil, err } return g.Map{ "dates": dates, "meeting": meetingList, "reservation": reservationList, }, nil } func (s Service) AppointTimeSplit() int { return g.Cfg().GetInt("reservation.time_split", 30) } type AppointTimeInfoAppoint struct { Id int `json:"id"` Title string `json:"title"` Start string `json:"start"` End string `json:"end"` Status int `json:"status"` Uid int `json:"uid"` Uname string `json:"uname"` Tel string `json:"tel"` Dept string `json:"dept"` } type AppointTimeInfoInstrument struct { Id int Weekday string BeginAt *gtime.Time EndAt *gtime.Time IsAppointAvailable int // 是否默认可预约 1是 2否 } type TimeSpan struct { Start *gtime.Time `json:"start"` End *gtime.Time `json:"end"` } // 获取指定仪器预约信息 func (s Service) AppointTimeInfo(req *meeting3.AppointTimeInfoReq, userinfo request.UserInfo) (map[string]interface{}, error) { if req.MeetingId == 0 { return nil, myerrors.NewMsgError(nil, "会议室 Id 不能为空") } if req.Date == nil { return nil, myerrors.NewMsgError(nil, "日期不能为空") } timesplit := s.AppointTimeSplit() days := common.Weekday(req.Date.Time) mettingSrv := meeting.NewSrv(s.Tenant) m, err := mettingSrv.Dao.Where("Id = ?", req.MeetingId).One() if err != nil { return nil, err } var appointInfo []AppointTimeInfoAppoint err = s.Dao.DB.Table("meeting_reservation a").LeftJoin("base_user b", "a.UserId=b.Id").Unscoped(). Where("a.DeletedAt IS NULL"). Where("a.EntityId", req.MeetingId). WherePri("a.Status", 1). Where("a.EndTime > ?", days[0]). Where("a.EndTime <= ?", days[6].Add(time.Hour*24)). Fields("a.Id, a.Title, a.StartTime as Start, a.EndTime as End, a.Status, a.UserId as Uid, b.Realname as Uname, b.Mobile as Tel, b.DepartmentName as Dept"). Structs(&appointInfo) if err == sql.ErrNoRows { err = nil } if err != nil { return nil, err } reservationBeginStr := g.Cfg().GetString("reservation.begin", 30) reservationEndStr := g.Cfg().GetString("reservation.end", 30) reservationWeekdayStr := g.Cfg().GetString("reservation.weekday", 30) instrWeekStr := strings.Split(reservationWeekdayStr, ",") instrWeek := []int{} for _, i := range instrWeekStr { d, err := strconv.Atoi(i) if err != nil { continue } instrWeek = append(instrWeek, d) } toRemoveDay := []int{} for i := 1; i <= 7; i++ { if !common.SliceIntcontains(instrWeek, i) { toRemoveDay = append(toRemoveDay, i) } } reservationBegin, err := time.Parse("15:04:05", reservationBeginStr) if err != nil { return nil, err } reservationEnd, err := time.Parse("15:04:05", reservationEndStr) if err != nil { return nil, err } if m.BeginAt == nil { m.BeginAt = gtime.NewFromTime(reservationBegin) } if m.EndAt == nil { m.EndAt = gtime.NewFromTime(reservationEnd) } unavailable := []TimeSpan{} // 仪器设置的可预约范围外 for i, d := range days { if common.SliceIntcontains(toRemoveDay, i+1) { unavailable = append(unavailable, TimeSpan{ Start: gtime.NewFromTime(d), End: gtime.NewFromTime(d.Add(24 * time.Hour)), }) continue } if m.BeginAt.Hour() != 0 || m.BeginAt.Minute() != 0 { end := d.Add( time.Hour*time.Duration(m.BeginAt.Hour()) + time.Minute*time.Duration(m.BeginAt.Minute())) unavailable = append(unavailable, TimeSpan{ Start: gtime.NewFromTime(d), End: gtime.NewFromTime(end), }) } endat := m.EndAt.Add(time.Minute * time.Duration(timesplit)) if endat.Hour() != 0 || endat.Minute() != 0 { start := d.Add( time.Hour*time.Duration(endat.Hour()) + time.Minute*time.Duration(endat.Minute())) unavailable = append(unavailable, TimeSpan{ Start: gtime.NewFromTime(start), End: gtime.NewFromTime(d.Add(time.Hour * 24)), }) } } return map[string]interface{}{ "begin_at": m.BeginAt.Time.Format("15:04:05"), "ent_at": m.EndAt.Time.Format("15:04:05"), "time_split": timesplit, "unavailable": unavailable, "appoint": appointInfo, }, nil } // OverviewListByDay 按天预约概况 func (s Service) OverviewListByDay(ctx context.Context, req meeting3.OverviewReq) (g.Map, error) { beginAt := g.Cfg().GetString("reservation.begin") endAt := g.Cfg().GetString("reservation.end") defaultPeriod := s.getCurrentPeriod(beginAt, endAt) dates := getCurrentWeekDay(req.Date) meetingList, err := meeting.NewSrv(s.Tenant).ShortList() if err != nil { return nil, err } reservationList, err := s.getCurrentDayReservation(ctx, meetingList, req.Date) if err != nil { return nil, err } // for i := range meetingList { // begin, end := beginAt, endAt // if meetingList[i].BeginAt != nil && meetingList[i].EndAt != nil { // begin = meetingList[i].BeginAt.Time.Format("15:04:05") // end = meetingList[i].EndAt.Time.Format("15:04:05") // } // fmt.Println(meetingList[i].Id, meetingList[i].Name) // period := s.getCurrentPeriod(begin, end) // meetingList[i].Period = period // } return g.Map{ "period": defaultPeriod, "dates": dates, "meeting": meetingList, "reservation": reservationList, }, nil } // ReserveInfo 预约信息 func (s Service) ReserveInfo(ctx context.Context, req meeting3.ReserveReq) (g.Map, error) { // 常规信息 beginAt := g.Cfg().GetString("reservation.begin_at") endAt := g.Cfg().GetString("reservation.end_at") period := s.getCurrentPeriod(beginAt, endAt) dates := getCurrentWeekDay(req.Date) weekday := g.Cfg().GetString("reservation.weekday") // 预约信息 list, err := s.getCurrentWeekReservation(ctx, req) if err != nil { return nil, err } return g.Map{ "period": period, "dates": dates, "weekday": weekday, "reservation": list, }, nil } // Ending 结束会议 func (s Service) Ending(ctx context.Context, req meeting3.EndingReq) error { // 先查询预约记录 entity, err := s.Dao.WherePri(req.Id).FindOne() if err != nil { return err } if entity == nil { return errors.New("未找到当前预约信息") } if entity.SignOutTime.String() != "" { return errors.New("当前预约已提前结束") } // 校验日期是不是同一天(24:00除外) 校验日期是不是在预约开始时间之后 if req.Date.Sub(entity.StartTime) <= 0 { return errors.New("结束时间不能早于会议预约时间") } if req.Date.Format("Y-m-d") != entity.StartTime.Format("Y-m-d") && req.Date.Hour() != 0 && req.Date.Minute() != 0 { return errors.New("结束时间必须为当天时间") } if (req.Date.Day()-1) != entity.StartTime.Day() && req.Date.Hour() == 0 && req.Date.Minute() == 0 { return errors.New("当前结束时间错误,请重试") } if req.Date.Sub(entity.EndTime) > 0 { return errors.New("预约时间正常结束的会议无需再次结束") } userInfo, err := micro_srv.GetUserInfo(ctx) if err != nil { return err } if userInfo.Id != int32(entity.UserId) { return errors.New("当前预约只能由本人结束") } // 更新实际结束时间 updatedMap := service.SetUpdatedMap(&userInfo) updatedMap[s.Dao.Columns.SignOutTime] = req.Date updatedMap[s.Dao.Columns.RealEndTimeNode] = common.GetNextTimeNode(req.Date) _, err = s.Dao.M.WherePri(req.Id).Update(updatedMap) if err != nil { return err } return nil } func (s Service) AutoEnding(id int) error { entity, err := s.Dao.WherePri(id).FindOne() if err != nil { return err } if entity == nil { return nil } if entity.SignOutTime != nil && entity.SignOutTime.String() != "" { return nil } _, err = s.Dao.WherePri(id).Update(g.Map{s.Dao.Columns.SignOutTime: entity.EndTime}) if err != nil { return err } return nil } // Check 预约校验 func (s Service) check(req meeting3.ReservationReq) error { // 校验开始结束时间不能跨天 if req.StartTime.Format("Y-m-d") != req.EndTime.Format("Y-m-d") && req.EndTime.Hour() != 0 && req.EndTime.Minute() != 0 { return errors.New("预约时间必须是同一天") } // 校验开始时间是否小于结束时间 if req.EndTime.Sub(req.StartTime) <= 0 { return errors.New("预约结束时候必须大于开始时间") } // 时间间隔不能小于30分钟 todo:可以加到配置参数中 if req.EndTime.Sub(req.StartTime).Minutes() < 30 { return errors.New("预约时间最小为30分钟") } // 校验是否是过期时间 if req.StartTime.Sub(gtime.Now()) < 0 { return errors.New("当前预约时间已过期") } // 校验时间内是否有 预约 entityModel := s.Dao.M. Where(s.Dao.Columns.EntityId, req.EntityId). Where(s.Dao.Columns.Status, 1). Where(s.Dao.Columns.StartTime+">= ? AND "+s.Dao.Columns.StartTime+"< ?", req.StartTime, req.EndTime) entityModel = entityModel. WhereOr(s.Dao.Columns.EntityId+"= ? AND "+s.Dao.Columns.Status+" = 1 AND "+ s.Dao.Columns.RealEndTimeNode+"> ? AND "+s.Dao.Columns.RealEndTimeNode+"<= ?", req.EntityId, req.StartTime, req.EndTime) entityModel = entityModel. WhereOr(s.Dao.Columns.EntityId+"= ? AND "+s.Dao.Columns.Status+" = 1 AND "+ s.Dao.Columns.RealEndTimeNode+">= ? AND "+s.Dao.Columns.StartTime+"<= ?", req.EntityId, req.EndTime, req.StartTime) exist, err := entityModel.Count() if err != nil { return err } if exist > 0 { return errors.New("当前时间段内已有预约记录,请刷新后重试") } // 校验会议室是否存在 err = meeting.NewSrv(s.Tenant).Exists(req.EntityId) if err != nil { return err } return nil } // getCurrentWeekDay 返回当前日期的周信息 func getCurrentWeekDay(current *gtime.Time) []meeting3.CurrentDate { startOfWeek := common.GetCNStartOfWeek(current) dates := make([]meeting3.CurrentDate, 0) for i := 0; i <= 6; i++ { times := startOfWeek.AddDate(0, 0, 1*i) dates = append(dates, meeting3.CurrentDate{ Date: times.Format("Y-m-d"), Day: times.Day(), }) } return dates } func (s Service) shortList(date *gtime.Time) ([]meeting3.ShortList, error) { sTime := common.GetCNStartOfWeek(date) eTime := common.GetCNEndOfWeek(date) list := make([]meeting3.ShortList, 0) res, err := s.Dao.M.WhereBetween(s.Dao.Columns.StartTime, sTime, eTime). Where(s.Dao.Columns.Status, 1).FindAll() if err != nil { return nil, err } if res.IsEmpty() { return nil, nil } res.Structs(&list) for k, _ := range list { // 当签退时间小于预约结束时间,使用签退时间 if list[k].SignOutTime.String() != "" && list[k].EndTime.Sub(list[k].SignOutTime) > 0 { list[k].EndTime = common.GetNextTimeNode(list[k].SignOutTime) } } return list, nil } // getCurrentDayTimeQuantum 获取当前的时间段 func (s Service) getCurrentPeriod(entityBeginAt, entityEndAt string) [][]string { now := gtime.Now() startTime, _ := gtime.StrToTime(entityBeginAt) //endTime, _ := gtime.StrToTime(entityEndAt) startAtStr := fmt.Sprintf("%v-%.2v-%.2v %.2v:%.2v:%.2v", now.Year(), int(now.Month()), now.Day(), startTime.Hour(), startTime.Minute(), startTime.Second()) startAt, _ := gtime.StrToTime(startAtStr) endAtStr := fmt.Sprintf("%v-%.2v-%.2v %s", now.Year(), int(now.Month()), now.Day(), entityEndAt) endAt, _ := gtime.StrToTime(endAtStr) timeLong := endAt.Sub(startAt).Hours() grids := int(timeLong * 2) fmt.Println(startAt, endAt) var timeQuantum [][]string for i := 0; i < grids; i++ { times := make([]string, 0) timeSplit := g.Cfg().GetInt("reservation.time_split") // 开始时间 startAt := startAt.Add(time.Duration(i*timeSplit) * time.Minute) startTime := startAt.Format("H:i") // 结束时间 endAt := startAt.Add(time.Duration(timeSplit) * time.Minute).Format("H:i") if startAt.Hour() == 23 && startAt.Minute() == 30 { endAt = "24:00" } times = append(times, startTime, endAt) timeQuantum = append(timeQuantum, times) } return timeQuantum } // getCurrentWeekReservation 返回本周预约信息 func (s Service) getCurrentWeekReservation(ctx context.Context, req meeting3.ReserveReq) ([]meeting3.ReservationList, error) { // 获取符合条件的预约信息 list, err := s.Dao.Where(s.Dao.Columns.EntityId, req.EntityId). Where(s.Dao.Columns.Status, 1). Where(s.Dao.Columns.StartTime+">=", common.GetCNStartOfWeek(req.Date)). Where(s.Dao.Columns.StartTime+"<=", common.GetCNEndOfWeek(req.Date)). FindAll() if err != nil { return nil, err } if len(list) == 0 { return nil, nil } uIds := make([]int64, 0) uMap := make(map[int]struct{}) for _, v := range list { if _, ok := uMap[v.UserId]; !ok { uIds = append(uIds, int64(v.UserId)) uMap[v.UserId] = struct{}{} } } // 获取用户信息 userList, err := srv.GetUserList(ctx, uIds, s.Tenant) usersMap := make(map[int]user_def.UserInfoList) for _, v := range userList { usersMap[v.Id] = v } resultList := make([]meeting3.ReservationList, 0) for _, v := range list { timeLong := v.EndTime.Sub(v.StartTime).Hours() if v.SignOutTime.String() != "" && v.EndTime.Sub(v.SignOutTime) > 0 { timeLong = common.GetNextTimeNode(v.SignOutTime).Sub(v.StartTime).Hours() } grid := timeLong * 2 for i := 0; i < int(grid); i++ { startAt := v.StartTime.Add(time.Duration(i) * 30 * time.Minute) resultList = append(resultList, meeting3.ReservationList{ ReservationId: int(v.Id), Day: v.StartTime.Day(), Dept: usersMap[v.UserId].DepartmentName, StartTime: fmt.Sprintf("%.2v:%.2v", startAt.Hour(), startAt.Minute()), Tel: usersMap[v.UserId].Mobile, Uid: v.UserId, Uname: usersMap[v.UserId].Realname, Week: common.GetCNWeekday(v.StartTime), }) } } return resultList, nil } // getCurrentWeekReservation 返回本周预约信息 func (s Service) getCurrentDayReservation(ctx context.Context, meetingList []meeting3.ShortMeeting, reqDate *gtime.Time) ([]meeting3.ReservationList, error) { // 获取符合条件的预约信息 resultList := make([]meeting3.ReservationList, 0) for _, value := range meetingList { list, err := s.Dao.Where(s.Dao.Columns.EntityId, value.Id). Where(s.Dao.Columns.Status, 1). Where(s.Dao.Columns.StartTime+">=", reqDate). Where(s.Dao.Columns.EndTime+"<=", reqDate.AddDate(0, 0, 1)). FindAll() if err != nil { g.Log().Info("getCurrentDayReservation err:", err) return nil, err } if len(list) == 0 { continue } uIds := make([]int64, 0) uMap := make(map[int]struct{}) for _, v := range list { if _, ok := uMap[v.UserId]; !ok { uIds = append(uIds, int64(v.UserId)) uMap[v.UserId] = struct{}{} } } // 获取用户信息 userList, err := srv.GetUserList(ctx, uIds, s.Tenant) usersMap := make(map[int]user_def.UserInfoList) for _, v := range userList { usersMap[v.Id] = v } for _, v := range list { timeLong := v.EndTime.Sub(v.StartTime).Hours() if v.SignOutTime.String() != "" && v.EndTime.Sub(v.SignOutTime) > 0 { timeLong = common.GetNextTimeNode(v.SignOutTime).Sub(v.StartTime).Hours() } grid := timeLong * 2 for i := 0; i < int(grid); i++ { startAt := v.StartTime.Add(time.Duration(i) * 30 * time.Minute) resultList = append(resultList, meeting3.ReservationList{ EntityId: value.Id, ReservationId: int(v.Id), Title: v.Title, Day: v.StartTime.Day(), Dept: usersMap[v.UserId].DepartmentName, StartTime: fmt.Sprintf("%.2v:%.2v", startAt.Hour(), startAt.Minute()), Tel: usersMap[v.UserId].Mobile, Uid: v.UserId, Uname: usersMap[v.UserId].Realname, Week: common.GetCNWeekday(v.StartTime), ReservationStartTime: v.StartTime.String(), ReservationEndTime: v.EndTime.String(), }) } } } return resultList, nil }