package model
import (
"time"
"git.coco.study/fvitt/good2go/internal/utils"
)
// Appointment sort of works like a timeslot
type Appointment struct {
StartDate time.Time
EndDate time.Time
Duration time.Duration
}
// New Constructor for Appointment, you should create appointments only with this function.
// For example: `appoint := Appointment{}.New(start, duration)`
// Takes only startDate and duration, calculates EndDate
func (b Appointment) New(start string, duration string) (a Appointment, err error) {
b.StartDate = utils.FormatTime(start)
b.Duration, err = time.ParseDuration(duration)
b.EndDate = b.StartDate.Add(b.Duration)
return b, err
}
package model
import (
"errors"
"sort"
"git.coco.study/fvitt/good2go/internal/utils"
)
// Room Room Entity
type Room struct {
Number int `bson:"number" json:"number"`
Capacity int `bson:"capacity" json:"capacity"`
Appointments []Appointment `bson:"appointments" json:"appointments"`
}
// AddAppointment adds an appointment to the room
func (r *Room) AddAppointment(appointment Appointment) {
r.Appointments = append(r.Appointments, appointment)
// sort so index 0 is the earliest appointment
sort.Slice(r.Appointments, func(i, j int) bool {
return r.Appointments[i].StartDate.Before(r.Appointments[j].StartDate)
})
}
// DeleteAppointment deletes an appointment on room level.
// Takes just the startDate, since their can only be one Appointment with this date.
func (r *Room) DeleteAppointment(startDate string) {
for index, appoint := range r.Appointments {
if appoint.StartDate.Equal(utils.FormatTime(startDate)) {
r.Appointments = append(r.Appointments[:index], r.Appointments[index+1:len(r.Appointments)]...)
}
}
}
// GetAppointment returns one single appointment based on startDate
func (r *Room) GetAppointment(startDate string) (appointment *Appointment, err error) {
for index, appoint := range r.Appointments {
if appoint.StartDate.Equal(utils.FormatTime(startDate)) {
appointment = &r.Appointments[index]
}
}
if appointment == nil {
err = errors.New("no appointment found.")
}
return appointment, err
}
package services
import (
r "git.coco.study/fvitt/good2go/database/mongo"
"git.coco.study/fvitt/good2go/internal/model"
)
type appointmentService struct {
}
var (
AppointmentService = &appointmentService{}
)
// AddAppointment books a room on a date.
// returns the assigned room or an error if no room is free
func (a *appointmentService) AddAppointment(reqAppoint model.Appointment) (room *model.Room, err error) {
selectedRooms, err := RoomService.FindAvailableRooms(reqAppoint)
// Add Appointment to room with lowest capacity
if len(selectedRooms) > 0 {
selectedRooms[0].AddAppointment(reqAppoint)
room = selectedRooms[0]
}
return room, err
}
// DeleteAppointment deletes an appointment on higher level.
// Takes room number and start date, finds room and deletes appointment.
func (a *appointmentService) DeleteAppointment(roomNo int, startDate string) (err error) {
room, err := r.RoomRepo.GetRoomByNumber(roomNo)
if err != nil {
return err
}
room.DeleteAppointment(startDate)
return
}
// GetAppointments returns all appointments in a map, where roomNumber is the key
func (a *appointmentService) GetAppointments() (appointmentsMap map[int][]model.Appointment) {
appointmentsMap = make(map[int][]model.Appointment)
rooms := r.RoomRepo.GetAllRooms()
for _, room := range rooms {
appointmentsMap[room.Number] = room.Appointments
}
return appointmentsMap
}
// GetAppointment gets a specific appointment in a room, returns map where roomNumber is key
func (a *appointmentService) GetAppointment(roomID string, startDate string) (appointmentMap map[int]*model.Appointment, err error) {
appointmentMap = make(map[int]*model.Appointment)
// get the room
room, err := r.RoomRepo.GetRoomByID(roomID)
if room == nil {
return nil, err
}
// create the appointment
appointment, err := room.GetAppointment(startDate)
if err == nil {
appointmentMap[room.Number] = appointment
}
return appointmentMap, err
}
package services
import (
"errors"
"sort"
"strings"
r "git.coco.study/fvitt/good2go/database/mongo"
model "git.coco.study/fvitt/good2go/internal/model"
"git.coco.study/fvitt/good2go/internal/utils"
)
type roomService struct{}
var (
// RoomService is a service for rooms
RoomService = &roomService{}
)
func (b *roomService) CreateRoom(room *model.Room) (*model.Room, error) {
return r.RoomRepo.CreateRoom(room)
}
func (b *roomService) GetRoomByID(roomID string) (room *model.Room, err error) {
room, err = r.RoomRepo.GetRoomByID(roomID)
if room == nil {
err = errors.New("room not found")
}
return room, err
}
func (b *roomService) GetRoomByNumber(roomNumber int) (room *model.Room, err error) {
room, err = r.RoomRepo.GetRoomByNumber(roomNumber)
if room == nil {
err = errors.New("room not found")
}
return room, err
}
func (b *roomService) GetAllRooms() []*model.Room {
return r.RoomRepo.GetAllRooms()
}
func (b *roomService) DeleteRoom(roomNumber int) (err error) {
err = r.RoomRepo.DeleteRoom(roomNumber)
return err
}
func (b *roomService) UpdateRoomCapacity(roomNumber int, newCapacity int) (room *model.Room, err error) {
oldRoom, err := r.RoomRepo.GetRoomByNumber(roomNumber)
if err != nil {
return nil, err
}
oldRoom.Capacity = newCapacity
return r.RoomRepo.UpdateRoom(oldRoom)
}
// FindAvailableRooms takes an appointment and returns all rooms which are free at this time.
// List is sorted after capacity, so lowest capacity is index 0.
func (b *roomService) FindAvailableRooms(reqAppoint model.Appointment) (selectedRooms []*model.Room, err error) {
// check opening times
var workdays = []string{"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}
reqStartTime := utils.FormatDateToHour(reqAppoint.StartDate)
reqEndTime := utils.FormatDateToHour(reqAppoint.EndDate)
buildStartTime := utils.FormatHour("08:00")
buildEndTime := utils.FormatHour("20:00")
workdaysString := []string{}
if reqStartTime.Before(buildStartTime) {
err = errors.New("start time is before building is open, startTime: " + reqStartTime.String() + " buildStart: " + buildStartTime.String())
return
}
if reqStartTime.After(buildEndTime) {
err = errors.New("start time is after building is closed, startTime: " + reqStartTime.String() + " buildingEnd: " + buildEndTime.String())
return
}
if reqEndTime.After(buildEndTime) {
err = errors.New("end time is after building is closed, endTime: " + reqEndTime.String() + " buildingEnd:" + buildEndTime.String())
return
}
for _, day := range workdays {
if day == reqAppoint.StartDate.Weekday().String() {
err = nil
break
}
workdaysString = append(workdaysString, day)
err = errors.New("startDate is not when building is open, startDate: " + reqAppoint.StartDate.Weekday().String() + " building open: " + strings.Join(workdaysString, ","))
}
allRooms := r.RoomRepo.GetAllRooms()
// look for free rooms
for _, room := range allRooms {
added := false
if len(room.Appointments) > 0 {
// loop appointments in each room
for _, appoint := range room.Appointments {
if reqAppoint.StartDate.After(appoint.EndDate) || reqAppoint.EndDate.Before(appoint.StartDate) {
if !added {
selectedRooms = append(selectedRooms, room)
added = true
}
} else {
// another appointment collides
if added {
// remove latest selected room
selectedRooms = selectedRooms[:len(selectedRooms)-1]
}
}
}
} else {
selectedRooms = append(selectedRooms, room)
}
}
if len(selectedRooms) <= 0 {
// no rooms found
err = errors.New("no room found for date")
} else {
// sort after capacity … lowest on index 0
sort.Slice(selectedRooms, func(i, j int) bool {
return selectedRooms[i].Capacity < selectedRooms[j].Capacity
})
}
return selectedRooms, err
}
package utils
import (
"strconv"
"strings"
"time"
)
// Go Lang reference Time:
// Mon Jan 2 15:04:05 MST 2006
func FormatTime(inputTime string) time.Time {
const timeLayout string = "02-01-2006 15:04"
formattedTime, _ := time.Parse(timeLayout, inputTime)
return formattedTime
}
func FormatHour(input string) time.Time {
const timeLayout string = "15:04"
formattedTime, _ := time.Parse(timeLayout, input)
return formattedTime
}
func FormatDateToHour(date time.Time) (formattedTime time.Time) {
const timeLayout string = "15:04"
hourString := strconv.Itoa(date.Hour())
minuteString := strconv.Itoa(date.Minute())
dateString := hourString + ":" + minuteString
formattedTime, _ = time.Parse(timeLayout, dateString)
return formattedTime
}
func FormatDay(input string) time.Weekday {
input = strings.ToLower(input)
//const timeLayout string = "Mon"
switch input {
case "mon":
return time.Monday
case "tue":
return time.Tuesday
case "wed":
return time.Wednesday
case "thu":
return time.Thursday
case "fri":
return time.Friday
case "sat":
return time.Saturday
case "sun":
return time.Sunday
default:
return time.Weekday(0)
}
//formattedTime, _ := time.Parse(timeLayout, input)
//return formattedTime.Weekday()
}