From eaa49a8c0d54d8ed2e487c006c369f8da8dd3f1d Mon Sep 17 00:00:00 2001 From: Mia Bennett Date: Thu, 27 Mar 2025 14:18:09 +0930 Subject: [PATCH 01/15] feat(auto_release) [PPT-1903] all_day_start time setting --- drivers/place/auto_release.cr | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/place/auto_release.cr b/drivers/place/auto_release.cr index d65682fa686..92c9a31b43b 100644 --- a/drivers/place/auto_release.cr +++ b/drivers/place/auto_release.cr @@ -25,9 +25,11 @@ class Place::AutoRelease < PlaceOS::Driver # - wfh: Work From Home # - aol: Away on Leave # - wfo: Work From Office - skip_created_after_start: true, # Skip bookings created after the start time - skip_same_day: false, # Skip bookings created on the same day as the booking + skip_created_after_start: true, # Skip bookings created after the start time + skip_same_day: false, # Skip bookings created on the same day as the booking asset_cache_timeout: 3600_i64, # 1 hour + all_day_start: 8.0, # Start time used for all day bookings + asset_cache_timeout: 3600_i64, # 1 hour }) accessor staff_api : StaffAPI_1 @@ -64,6 +66,7 @@ class Place::AutoRelease < PlaceOS::Driver @auto_release : AutoReleaseConfig = AutoReleaseConfig.new @skip_created_after_start : Bool = true @skip_same_day : Bool = true + @all_day_start : Float64 = 8.0 def on_update @building_zone = nil @@ -83,6 +86,7 @@ class Place::AutoRelease < PlaceOS::Driver @auto_release = setting?(AutoReleaseConfig, :auto_release) || AutoReleaseConfig.new @skip_created_after_start = setting?(Bool, :skip_created_after_start) || true @skip_same_day = setting?(Bool, :skip_same_day) || false + @all_day_start = setting?(Float64, :all_day_start) || 8.0 @asset_cache_timeout = setting?(Int64, :asset_cache_timeout) || 3600_i64 clear_asset_cache @@ -206,6 +210,9 @@ class Place::AutoRelease < PlaceOS::Driver # e.g. 7:30AM = 7.5 event_time = booking_start.hour + (booking_start.minute / 60.0) + # use all_day_start for all day bookings + event_time = @all_day_start if booking.all_day + # exclude overrides with empty time blocks overrides = preferences[:work_overrides].select { |_, pref| pref.blocks.size > 0 } @@ -250,6 +257,8 @@ class Place::AutoRelease < PlaceOS::Driver bookings.each do |booking| next if previously_released.includes? booking.id + # convert hours (all_day_start) to seconds + booking_start = booking.all_day ? (@all_day_start * 60 * 60).to_i : booking.booking_start # convert minutes (time_after) to seconds for comparison with unix timestamps (booking_start) if Time.utc.to_unix - booking.booking_start > @auto_release.time_after * 60 # skip if there's been changes to the cached bookings checked_in status or booking_start time From f94f22dc85e756c87b640c639e66b4c48ceb0e08 Mon Sep 17 00:00:00 2001 From: Mia Bennett Date: Thu, 27 Mar 2025 14:20:53 +0930 Subject: [PATCH 02/15] feat(auto_release): [PPT-1901] default_work_preferences setting --- drivers/place/auto_release.cr | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/place/auto_release.cr b/drivers/place/auto_release.cr index 92c9a31b43b..6d9b0d8f7f0 100644 --- a/drivers/place/auto_release.cr +++ b/drivers/place/auto_release.cr @@ -27,7 +27,7 @@ class Place::AutoRelease < PlaceOS::Driver # - wfo: Work From Office skip_created_after_start: true, # Skip bookings created after the start time skip_same_day: false, # Skip bookings created on the same day as the booking - asset_cache_timeout: 3600_i64, # 1 hour + default_work_preferences: [] of WorktimePreference, # Default work preferences for users all_day_start: 8.0, # Start time used for all day bookings asset_cache_timeout: 3600_i64, # 1 hour }) @@ -66,6 +66,7 @@ class Place::AutoRelease < PlaceOS::Driver @auto_release : AutoReleaseConfig = AutoReleaseConfig.new @skip_created_after_start : Bool = true @skip_same_day : Bool = true + @default_work_preferences : Array(WorktimePreference) = [] of WorktimePreference @all_day_start : Float64 = 8.0 def on_update @@ -86,6 +87,7 @@ class Place::AutoRelease < PlaceOS::Driver @auto_release = setting?(AutoReleaseConfig, :auto_release) || AutoReleaseConfig.new @skip_created_after_start = setting?(Bool, :skip_created_after_start) || true @skip_same_day = setting?(Bool, :skip_same_day) || false + @default_work_preferences = setting?(Array(WorktimePreference), :default_work_preferences) || [] of WorktimePreference @all_day_start = setting?(Float64, :all_day_start) || 8.0 @asset_cache_timeout = setting?(Int64, :asset_cache_timeout) || 3600_i64 @@ -157,6 +159,8 @@ class Place::AutoRelease < PlaceOS::Driver user = staff_api.user(user_id).get work_preferences = Array(WorktimePreference).from_json user.as_h["work_preferences"].to_json + work_preferences = @default_work_preferences if work_preferences.empty? + work_overrides = Hash(String, WorktimePreference).from_json user.as_h["work_overrides"].to_json {work_preferences: work_preferences, work_overrides: work_overrides} From 3aeb16658654a97e67f929c3c14de9da934ed652 Mon Sep 17 00:00:00 2001 From: Mia Bennett Date: Thu, 27 Mar 2025 14:22:40 +0930 Subject: [PATCH 03/15] feat(auto_release): [PPT-1900] release_outside_hours setting --- drivers/place/auto_release.cr | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/place/auto_release.cr b/drivers/place/auto_release.cr index 6d9b0d8f7f0..35990748f0a 100644 --- a/drivers/place/auto_release.cr +++ b/drivers/place/auto_release.cr @@ -28,6 +28,7 @@ class Place::AutoRelease < PlaceOS::Driver skip_created_after_start: true, # Skip bookings created after the start time skip_same_day: false, # Skip bookings created on the same day as the booking default_work_preferences: [] of WorktimePreference, # Default work preferences for users + release_outside_hours: false, # Release bookings outside of work hours all_day_start: 8.0, # Start time used for all day bookings asset_cache_timeout: 3600_i64, # 1 hour }) @@ -67,6 +68,7 @@ class Place::AutoRelease < PlaceOS::Driver @skip_created_after_start : Bool = true @skip_same_day : Bool = true @default_work_preferences : Array(WorktimePreference) = [] of WorktimePreference + @release_outside_hours : Bool = false @all_day_start : Float64 = 8.0 def on_update @@ -88,6 +90,7 @@ class Place::AutoRelease < PlaceOS::Driver @skip_created_after_start = setting?(Bool, :skip_created_after_start) || true @skip_same_day = setting?(Bool, :skip_same_day) || false @default_work_preferences = setting?(Array(WorktimePreference), :default_work_preferences) || [] of WorktimePreference + @release_outside_hours = setting?(Bool, :release_outside_hours) || false @all_day_start = setting?(Float64, :all_day_start) || 8.0 @asset_cache_timeout = setting?(Int64, :asset_cache_timeout) || 3600_i64 @@ -228,6 +231,8 @@ class Place::AutoRelease < PlaceOS::Driver elsif (preference = preferences[:work_preferences].find { |pref| pref.day_of_week == day_of_week }) && in_preference?(preference, event_time, @release_locations) results << booking + elsif @release_outside_hours + results << booking end end end From 50126996b969bb8aa02e4e463b222d5b64d3e582 Mon Sep 17 00:00:00 2001 From: Mia Bennett Date: Thu, 27 Mar 2025 14:25:23 +0930 Subject: [PATCH 04/15] feat(auto_release): [MT-346] unique_templates setting --- drivers/place/auto_release.cr | 89 ++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 37 deletions(-) diff --git a/drivers/place/auto_release.cr b/drivers/place/auto_release.cr index 35990748f0a..ca6b04c822e 100644 --- a/drivers/place/auto_release.cr +++ b/drivers/place/auto_release.cr @@ -19,6 +19,7 @@ class Place::AutoRelease < PlaceOS::Driver date_format: "%A, %-d %B", email_schedule: "*/5 * * * *", email_template: "auto_release", + unique_templates: false, time_window_hours: 4, # The number of hours to check for bookings pending release release_locations: ["wfh", "aol"], # Locations to release bookings for # available locations: @@ -60,10 +61,11 @@ class Place::AutoRelease < PlaceOS::Driver @auto_release_email_errors : UInt64 = 0_u64 @email_template : String = "auto_release" + @unique_templates : Bool = false @email_schedule : String? = nil @time_window_hours : Int32 = 1 - @release_locations : Array(String) = ["wfh"] + @release_locations : Array(String) = ["wfh", "aol"] @auto_release : AutoReleaseConfig = AutoReleaseConfig.new @skip_created_after_start : Bool = true @skip_same_day : Bool = true @@ -79,13 +81,14 @@ class Place::AutoRelease < PlaceOS::Driver @email_schedule = setting?(String, :email_schedule).presence @email_template = setting?(String, :email_template) || "auto_release" + @unique_templates = setting?(Bool, :unique_templates) || false @date_time_format = setting?(String, :date_time_format) || "%c" @time_format = setting?(String, :time_format) || "%l:%M%p" @date_format = setting?(String, :date_format) || "%A, %-d %B" @time_window_hours = setting?(Int32, :time_window_hours) || 1 - @release_locations = setting?(Array(String), :release_locations) || ["wfh"] + @release_locations = setting?(Array(String), :release_locations) || ["wfh", "aol"] @auto_release = setting?(AutoReleaseConfig, :auto_release) || AutoReleaseConfig.new @skip_created_after_start = setting?(Bool, :skip_created_after_start) || true @skip_same_day = setting?(Bool, :skip_same_day) || false @@ -287,38 +290,6 @@ class Place::AutoRelease < PlaceOS::Driver self[:released_booking_ids] = [] of Int64 end - def template_fields : Array(TemplateFields) - time_now = Time.utc.in(timezone) - [ - TemplateFields.new( - trigger: {@email_template, "auto_release"}, - name: "Auto release booking", - description: "Notification when a booking is pending automatic release due to user's work location preferences", - fields: [ - {name: "booking_id", description: "Unique identifier for the booking that may be released"}, - {name: "booking_start", description: "Unix timestamp of when the booking begins"}, - {name: "booking_end", description: "Unix timestamp of when the booking ends"}, - {name: "start_time", description: "Formatted start time (e.g., #{time_now.to_s(@time_format)})"}, - {name: "start_date", description: "Formatted start date (e.g., #{time_now.to_s(@date_format)})"}, - {name: "start_datetime", description: "Formatted start date and time (e.g., #{time_now.to_s(@date_time_format)})"}, - {name: "end_time", description: "Formatted end time (e.g., #{time_now.to_s(@time_format)})"}, - {name: "end_date", description: "Formatted end date (e.g., #{time_now.to_s(@date_format)})"}, - {name: "end_datetime", description: "Formatted end date and time (e.g., #{time_now.to_s(@date_time_format)})"}, - {name: "asset_id", description: "Identifier of the booked resource"}, - {name: "asset_name", description: "Name of the booked resource"}, - {name: "user_id", description: "Identifier of the person who has the booking"}, - {name: "user_email", description: "Email address of the person who has the booking"}, - {name: "user_name", description: "Full name of the person who has the booking"}, - {name: "reason", description: "Title or purpose of the booking"}, - {name: "approver_name", description: "Name of the person who approved the booking"}, - {name: "approver_email", description: "Email of the person who approved the booking"}, - {name: "booked_by_name", description: "Name of the person who made the booking"}, - {name: "booked_by_email", description: "Email of the person who made the booking"}, - ] - ), - ] - end - @[Security(Level::Support)] def send_release_emails emailed_booking_ids = [] of Int64 @@ -374,7 +345,7 @@ class Place::AutoRelease < PlaceOS::Driver begin mailer.send_template( to: booking.user_email, - template: {@email_template, "auto_release"}, + template: {@email_template, "auto_release#{template_suffix(booking.booking_type)}"}, args: args) emailed_booking_ids << booking.id rescue error @@ -385,8 +356,52 @@ class Place::AutoRelease < PlaceOS::Driver self[:emailed_booking_ids] = emailed_booking_ids end - # time_before and time_after are in minutes - record AutoReleaseConfig, time_before : Int64 = 0, time_after : Int64 = 0, resources : Array(String) = [] of String do + def template_fields : Array(TemplateFields) + if @unique_templates && !@auto_release.resources.empty? + @auto_release.resources.map { |type| unique_template_fields(type) } + else + [unique_template_fields] + end + end + + private def unique_template_fields(booking_type : String = "") : TemplateFields + time_now = Time.utc.in(timezone) + + TemplateFields.new( + trigger: {@email_template, "auto_release#{template_suffix(booking_type)}"}, + name: "Auto release booking#{template_fields_suffix(booking_type)}", + description: "Notification when a booking is pending automatic release due to user's work location preferences", + fields: [ + {name: "booking_id", description: "Unique identifier for the booking that may be released"}, + {name: "booking_start", description: "Unix timestamp of when the booking begins"}, + {name: "booking_end", description: "Unix timestamp of when the booking ends"}, + {name: "start_time", description: "Formatted start time (e.g., #{time_now.to_s(@time_format)})"}, + {name: "start_date", description: "Formatted start date (e.g., #{time_now.to_s(@date_format)})"}, + {name: "start_datetime", description: "Formatted start date and time (e.g., #{time_now.to_s(@date_time_format)})"}, + {name: "end_time", description: "Formatted end time (e.g., #{time_now.to_s(@time_format)})"}, + {name: "end_date", description: "Formatted end date (e.g., #{time_now.to_s(@date_format)})"}, + {name: "end_datetime", description: "Formatted end date and time (e.g., #{time_now.to_s(@date_time_format)})"}, + {name: "asset_id", description: "Identifier of the booked resource"}, + {name: "asset_name", description: "Name of the booked resource"}, + {name: "user_id", description: "Identifier of the person who has the booking"}, + {name: "user_email", description: "Email address of the person who has the booking"}, + {name: "user_name", description: "Full name of the person who has the booking"}, + {name: "reason", description: "Title or purpose of the booking"}, + {name: "approver_name", description: "Name of the person who approved the booking"}, + {name: "approver_email", description: "Email of the person who approved the booking"}, + {name: "booked_by_name", description: "Name of the person who made the booking"}, + {name: "booked_by_email", description: "Email of the person who made the booking"}, + ] + ) + end + + private def template_suffix(booking_type : String) : String + @unique_templates && !@auto_release.resources.empty? ? "_#{booking_type}" : "" + end + + private def template_fields_suffix(booking_type : String) : String + @unique_templates && !@auto_release.resources.empty? ? " (#{booking_type})" : "" + end include JSON::Serializable end From 5f3491c8075531f44a83b98a77ed3bd4f0c564f8 Mon Sep 17 00:00:00 2001 From: Mia Bennett Date: Thu, 27 Mar 2025 14:29:39 +0930 Subject: [PATCH 05/15] feat(auto_release): [MT-346] custom resource time_before/time_after --- drivers/place/auto_release.cr | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/drivers/place/auto_release.cr b/drivers/place/auto_release.cr index ca6b04c822e..73b3b53251a 100644 --- a/drivers/place/auto_release.cr +++ b/drivers/place/auto_release.cr @@ -126,13 +126,11 @@ class Place::AutoRelease < PlaceOS::Driver @[Security(Level::Support)] def enabled? : Bool if !@auto_release.resources.empty? && - (@auto_release.time_before > 0 || @auto_release.time_after > 0) && !building_zone.time_location?.nil? true else logger.notice { "auto release is not enabled on zone #{building_zone.id}" } logger.debug { "auto release is not enabled on zone #{building_zone.id} due to auto_release.resources being empty" } if @auto_release.resources.empty? - logger.debug { "auto release is not enabled on zone #{building_zone.id} due to auto_release.time_before and auto_release.time_after being 0" } if @auto_release.time_before.zero? && @auto_release.time_after.zero? logger.debug { "auto release is not enabled on zone #{building_zone.id} due to building_zone.time_location being nil" } if building_zone.time_location?.nil? false end @@ -272,7 +270,7 @@ class Place::AutoRelease < PlaceOS::Driver # convert hours (all_day_start) to seconds booking_start = booking.all_day ? (@all_day_start * 60 * 60).to_i : booking.booking_start # convert minutes (time_after) to seconds for comparison with unix timestamps (booking_start) - if Time.utc.to_unix - booking.booking_start > @auto_release.time_after * 60 + if Time.utc.to_unix - booking_start > @auto_release.time_after(booking.type) * 60 # skip if there's been changes to the cached bookings checked_in status or booking_start time next if skip_release?(booking) @@ -308,8 +306,8 @@ class Place::AutoRelease < PlaceOS::Driver # convert minutes (time_after) to seconds for comparison with unix timestamps (booking_start) if enabled? && - (booking.booking_start - Time.utc.to_unix < @auto_release.time_before * 60) && - (Time.utc.to_unix - booking.booking_start < @auto_release.time_after * 60) + (booking.booking_start - Time.utc.to_unix < @auto_release.time_before(booking.type) * 60) && + (Time.utc.to_unix - booking.booking_start < @auto_release.time_after(booking.type) * 60) logger.debug { "sending release email to #{booking.user_email} for booking #{booking.id} as it is withing the time_before window" } location = Time::Location.load(booking.timezone.presence || timezone.name) @@ -402,7 +400,32 @@ class Place::AutoRelease < PlaceOS::Driver private def template_fields_suffix(booking_type : String) : String @unique_templates && !@auto_release.resources.empty? ? " (#{booking_type})" : "" end + + struct AutoReleaseConfig include JSON::Serializable + include JSON::Serializable::Unmapped + + getter time_before : Int64 = 0 # minutes + getter time_after : Int64 = 0 # minutes + getter resources : Array(String) = [] of String + + # getter all_day_start : Float64 = 8.0 # hours + + def time_before(resource : String) : Int64 + if resource_time_before = json_unmapped["#{resource}_time_before"]? + resource_time_before.as_i64 + else + time_before + end + end + + def time_after(resource : String) : Int64 + if resource_time_after = json_unmapped["#{resource}_time_after"]? + resource_time_after.as_i64 + else + time_after + end + end end # start_time: Start time of work hours. e.g. `7.5` being 7:30AM From 9c4e885a4e36d43d449b9cd03965f87e4ec73d29 Mon Sep 17 00:00:00 2001 From: Mia Bennett Date: Mon, 31 Mar 2025 14:00:51 +0930 Subject: [PATCH 06/15] test(auto_release): remove tests for changed functionality --- drivers/place/auto_release_spec.cr | 34 +++++------------------------- 1 file changed, 5 insertions(+), 29 deletions(-) diff --git a/drivers/place/auto_release_spec.cr b/drivers/place/auto_release_spec.cr index d0698ab2c2f..8015d5c7817 100644 --- a/drivers/place/auto_release_spec.cr +++ b/drivers/place/auto_release_spec.cr @@ -941,42 +941,18 @@ DriverSpecs.mock_driver "Place::AutoRelease" do }) resp = exec(:enabled?).get resp.should eq nil - # enabled when time_before is set and time_after is 0 - settings({ - auto_release: { - time_before: 10, # notify before start of meeting - time_after: 0, # release at start of meeting - resources: ["desk"], - }, - }) - resp = exec(:enabled?).get - resp.should eq true - # enabled when time_before is 0 and time_after is set + + # enabled when there are resources settings({ auto_release: { - time_before: 0, # notify at start of meeting - time_after: 10, # release after start of meeting + time_before: 10, + time_after: 10, resources: ["desk"], }, }) resp = exec(:enabled?).get resp.should eq true - # enabled when both time_before and time_after are set - settings({ - auto_release: { - time_before: 10, # notify before start of meeting - time_after: 10, # release after start of meeting - resources: ["desk"], - }, - }) - # enabled when time_before is set to a negative number and time_after is set to a positive number - settings({ - auto_release: { - time_before: -10, # notify after start of meeting - time_after: 20, # release after start of meeting - resources: ["desk"], - }, - }) + # disabled when resources is empty resp = exec(:enabled?).get resp.should eq true From d4c2439e375f1049ae073ffda136f9e91e17ab51 Mon Sep 17 00:00:00 2001 From: Mia Bennett Date: Tue, 1 Apr 2025 12:57:58 +0930 Subject: [PATCH 07/15] feat(auto_release): [PPT-1903] all_day bookings --- drivers/place/auto_release.cr | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/place/auto_release.cr b/drivers/place/auto_release.cr index 73b3b53251a..951c390733c 100644 --- a/drivers/place/auto_release.cr +++ b/drivers/place/auto_release.cr @@ -270,7 +270,7 @@ class Place::AutoRelease < PlaceOS::Driver # convert hours (all_day_start) to seconds booking_start = booking.all_day ? (@all_day_start * 60 * 60).to_i : booking.booking_start # convert minutes (time_after) to seconds for comparison with unix timestamps (booking_start) - if Time.utc.to_unix - booking_start > @auto_release.time_after(booking.type) * 60 + if Time.utc.to_unix - booking_start > @auto_release.time_after(booking.booking_type) * 60 # skip if there's been changes to the cached bookings checked_in status or booking_start time next if skip_release?(booking) @@ -306,8 +306,8 @@ class Place::AutoRelease < PlaceOS::Driver # convert minutes (time_after) to seconds for comparison with unix timestamps (booking_start) if enabled? && - (booking.booking_start - Time.utc.to_unix < @auto_release.time_before(booking.type) * 60) && - (Time.utc.to_unix - booking.booking_start < @auto_release.time_after(booking.type) * 60) + (booking.booking_start - Time.utc.to_unix < @auto_release.time_before(booking.booking_type) * 60) && + (Time.utc.to_unix - booking.booking_start < @auto_release.time_after(booking.booking_type) * 60) logger.debug { "sending release email to #{booking.user_email} for booking #{booking.id} as it is withing the time_before window" } location = Time::Location.load(booking.timezone.presence || timezone.name) @@ -409,7 +409,8 @@ class Place::AutoRelease < PlaceOS::Driver getter time_after : Int64 = 0 # minutes getter resources : Array(String) = [] of String - # getter all_day_start : Float64 = 8.0 # hours + def initialize + end def time_before(resource : String) : Int64 if resource_time_before = json_unmapped["#{resource}_time_before"]? From 6ee4ec9432d27b1fc85f81d889f81ba5e97601b6 Mon Sep 17 00:00:00 2001 From: Mia Bennett Date: Tue, 1 Apr 2025 14:29:37 +0930 Subject: [PATCH 08/15] feat(auto_release): [PPT-1903] all_day bookings --- drivers/place/auto_release.cr | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/drivers/place/auto_release.cr b/drivers/place/auto_release.cr index 951c390733c..4975dd7a56e 100644 --- a/drivers/place/auto_release.cr +++ b/drivers/place/auto_release.cr @@ -28,6 +28,7 @@ class Place::AutoRelease < PlaceOS::Driver # - wfo: Work From Office skip_created_after_start: true, # Skip bookings created after the start time skip_same_day: false, # Skip bookings created on the same day as the booking + skip_all_day: false, # Skip all day bookings default_work_preferences: [] of WorktimePreference, # Default work preferences for users release_outside_hours: false, # Release bookings outside of work hours all_day_start: 8.0, # Start time used for all day bookings @@ -69,6 +70,7 @@ class Place::AutoRelease < PlaceOS::Driver @auto_release : AutoReleaseConfig = AutoReleaseConfig.new @skip_created_after_start : Bool = true @skip_same_day : Bool = true + @skip_all_day : Bool = false @default_work_preferences : Array(WorktimePreference) = [] of WorktimePreference @release_outside_hours : Bool = false @all_day_start : Float64 = 8.0 @@ -92,6 +94,7 @@ class Place::AutoRelease < PlaceOS::Driver @auto_release = setting?(AutoReleaseConfig, :auto_release) || AutoReleaseConfig.new @skip_created_after_start = setting?(Bool, :skip_created_after_start) || true @skip_same_day = setting?(Bool, :skip_same_day) || false + @skip_all_day = setting?(Bool, :skip_all_day) || false @default_work_preferences = setting?(Array(WorktimePreference), :default_work_preferences) || [] of WorktimePreference @release_outside_hours = setting?(Bool, :release_outside_hours) || false @all_day_start = setting?(Float64, :all_day_start) || 8.0 @@ -206,6 +209,7 @@ class Place::AutoRelease < PlaceOS::Driver next if @skip_created_after_start && (created_at = booking.created) && created_at >= booking.booking_start next if @skip_same_day && (created_at = booking.created) && Time.unix(created_at).in(building_zone.time_location!).day == Time.unix(booking.booking_start).in(building_zone.time_location!).day + next if @skip_all_day && booking.all_day if preferences = get_user_preferences?(booking.user_id) # get the booking start time in the building timezone @@ -267,8 +271,12 @@ class Place::AutoRelease < PlaceOS::Driver bookings.each do |booking| next if previously_released.includes? booking.id + # get the booking start time in the building timezone + booking_start = Time.unix(booking.booking_start).in building_zone.time_location! + # convert hours (all_day_start) to seconds - booking_start = booking.all_day ? (@all_day_start * 60 * 60).to_i : booking.booking_start + booking_start = booking.all_day ? all_day_start_time(booking_start).to_unix : booking.booking_start + # convert minutes (time_after) to seconds for comparison with unix timestamps (booking_start) if Time.utc.to_unix - booking_start > @auto_release.time_after(booking.booking_type) * 60 # skip if there's been changes to the cached bookings checked_in status or booking_start time @@ -288,6 +296,14 @@ class Place::AutoRelease < PlaceOS::Driver self[:released_booking_ids] = [] of Int64 end + private def all_day_start_time(booking_start : Time) : Time + # Convert float hours/minutes to time + # e.g. 7.5 = 7:30AM in the specified timezone + hours = @all_day_start.to_i + minutes = ((@all_day_start - hours) * 60).to_i + time_in_zone = Time.local(booking_start.year, booking_start.month, booking_start.day, hours, minutes, location: building_zone.time_location!) + end + @[Security(Level::Support)] def send_release_emails emailed_booking_ids = [] of Int64 From 4ca7f78ff2d5b45fbc9860f06e451634906d273e Mon Sep 17 00:00:00 2001 From: Mia Bennett Date: Tue, 1 Apr 2025 14:31:13 +0930 Subject: [PATCH 09/15] test(auto_release): [PPT-1903] all_day bookings --- drivers/place/auto_release_spec.cr | 267 ++++++++++++++++++++--------- 1 file changed, 186 insertions(+), 81 deletions(-) diff --git a/drivers/place/auto_release_spec.cr b/drivers/place/auto_release_spec.cr index 8015d5c7817..effdb25f62b 100644 --- a/drivers/place/auto_release_spec.cr +++ b/drivers/place/auto_release_spec.cr @@ -10,9 +10,11 @@ class StaffAPI < DriverSpecs::MockDriver self[:rejected] = self[:rejected].as_i + 1 end - TIMEZONE = "Australia/Sydney" - TIME_UTC = Time.utc - TIME_YESTERDAY = TIME_UTC - 1.day + TIMEZONE = "Australia/Sydney" + TIME_LOCAL = Time.local(location: Time::Location.load(TIMEZONE)) + TIME_YESTERDAY = TIME_LOCAL - 1.day + TIME_START_OF_DAY = TIME_LOCAL - TIME_LOCAL.hour.hours - TIME_LOCAL.minute.minutes - TIME_LOCAL.second.seconds + TIME_END_OF_DAY = TIME_START_OF_DAY + 1.day - 1.seconds # Using a constant for bookings to ensure the times don't change during tests BOOKINGS = [ @@ -24,9 +26,9 @@ class StaffAPI < DriverSpecs::MockDriver asset_id: "desk_001", zones: ["zone-1234"], booking_type: "desk", - booking_start: (TIME_UTC + 1.hour).to_unix, - booking_end: (TIME_UTC + 2.hours).to_unix, - timezone: "Australia/Darwin", + booking_start: (TIME_LOCAL + 1.hour).to_unix, + booking_end: (TIME_LOCAL + 2.hours).to_unix, + timezone: TIMEZONE, title: "ignore", description: "", checked_in: false, @@ -36,8 +38,8 @@ class StaffAPI < DriverSpecs::MockDriver booked_by_email: "user_one@example.com", booked_by_name: "User One", process_state: "approved", - last_changed: TIME_UTC.to_unix, - created: TIME_UTC.to_unix, + last_changed: TIME_LOCAL.to_unix, + created: TIME_LOCAL.to_unix, }, { id: 2, @@ -47,9 +49,9 @@ class StaffAPI < DriverSpecs::MockDriver asset_id: "desk_002", zones: ["zone-1234"], booking_type: "desk", - booking_start: (TIME_UTC + 5.minutes).to_unix, - booking_end: (TIME_UTC + 1.hour).to_unix, - timezone: "Australia/Darwin", + booking_start: (TIME_LOCAL + 5.minutes).to_unix, + booking_end: (TIME_LOCAL + 1.hour).to_unix, + timezone: TIMEZONE, title: "notify", description: "", checked_in: false, @@ -59,8 +61,8 @@ class StaffAPI < DriverSpecs::MockDriver booked_by_email: "user_one@example.com", booked_by_name: "User One", process_state: "approved", - last_changed: TIME_UTC.to_unix, - created: TIME_UTC.to_unix, + last_changed: TIME_LOCAL.to_unix, + created: TIME_LOCAL.to_unix, }, { id: 3, @@ -70,9 +72,9 @@ class StaffAPI < DriverSpecs::MockDriver asset_id: "desk_003", zones: ["zone-1234"], booking_type: "desk", - booking_start: (TIME_UTC - 11.minutes).to_unix, - booking_end: (TIME_UTC + 1.hour).to_unix, - timezone: "Australia/Darwin", + booking_start: (TIME_LOCAL - 11.minutes).to_unix, + booking_end: (TIME_LOCAL + 1.hour).to_unix, + timezone: TIMEZONE, title: "reject", description: "", checked_in: false, @@ -82,8 +84,8 @@ class StaffAPI < DriverSpecs::MockDriver booked_by_email: "user_one@example.com", booked_by_name: "User One", process_state: "approved", - last_changed: (TIME_UTC - 20.minutes).to_unix, - created: (TIME_UTC - 20.minutes).to_unix, + last_changed: (TIME_LOCAL - 20.minutes).to_unix, + created: (TIME_LOCAL - 20.minutes).to_unix, }, { id: 4, @@ -93,9 +95,9 @@ class StaffAPI < DriverSpecs::MockDriver asset_id: "desk_004", zones: ["zone-1234"], booking_type: "desk", - booking_start: (TIME_UTC + 5.hours).to_unix, - booking_end: (TIME_UTC + 6.hours).to_unix, - timezone: "Australia/Darwin", + booking_start: (TIME_LOCAL + 5.hours).to_unix, + booking_end: (TIME_LOCAL + 6.hours).to_unix, + timezone: TIMEZONE, title: "ignore_after_hours", description: "", checked_in: false, @@ -105,8 +107,8 @@ class StaffAPI < DriverSpecs::MockDriver booked_by_email: "user_one@example.com", booked_by_name: "User One", process_state: "approved", - last_changed: TIME_UTC.to_unix, - created: TIME_UTC.to_unix, + last_changed: TIME_LOCAL.to_unix, + created: TIME_LOCAL.to_unix, }, { id: 5, @@ -116,9 +118,9 @@ class StaffAPI < DriverSpecs::MockDriver asset_id: "desk_005", zones: ["zone-1234"], booking_type: "desk", - booking_start: (TIME_UTC - 11.minutes).to_unix, - booking_end: (TIME_UTC + 1.hour).to_unix, - timezone: "Australia/Darwin", + booking_start: (TIME_LOCAL - 11.minutes).to_unix, + booking_end: (TIME_LOCAL + 1.hour).to_unix, + timezone: TIMEZONE, title: "ignore_wfo", description: "", checked_in: false, @@ -128,8 +130,8 @@ class StaffAPI < DriverSpecs::MockDriver booked_by_email: "user_two@example.com", booked_by_name: "User Two", process_state: "approved", - last_changed: (TIME_UTC - 20.minutes).to_unix, - created: (TIME_UTC - 20.minutes).to_unix, + last_changed: (TIME_LOCAL - 20.minutes).to_unix, + created: (TIME_LOCAL - 20.minutes).to_unix, }, { id: 6, @@ -139,9 +141,9 @@ class StaffAPI < DriverSpecs::MockDriver asset_id: "desk_006", zones: ["zone-1234"], booking_type: "desk", - booking_start: (TIME_UTC - 11.minutes).to_unix, - booking_end: (TIME_UTC + 1.hour).to_unix, - timezone: "Australia/Darwin", + booking_start: (TIME_LOCAL - 11.minutes).to_unix, + booking_end: (TIME_LOCAL + 1.hour).to_unix, + timezone: TIMEZONE, title: "ignore_last_minute_checkin", description: "", checked_in: false, @@ -151,8 +153,8 @@ class StaffAPI < DriverSpecs::MockDriver booked_by_email: "user_one@example.com", booked_by_name: "User One", process_state: "approved", - last_changed: (TIME_UTC - 20.minutes).to_unix, - created: (TIME_UTC - 20.minutes).to_unix, + last_changed: (TIME_LOCAL - 20.minutes).to_unix, + created: (TIME_LOCAL - 20.minutes).to_unix, }, { id: 7, @@ -162,9 +164,9 @@ class StaffAPI < DriverSpecs::MockDriver asset_id: "desk_007", zones: ["zone-1234"], booking_type: "desk", - booking_start: (TIME_UTC - 11.minutes).to_unix, - booking_end: (TIME_UTC + 1.hour).to_unix, - timezone: "Australia/Darwin", + booking_start: (TIME_LOCAL - 11.minutes).to_unix, + booking_end: (TIME_LOCAL + 1.hour).to_unix, + timezone: TIMEZONE, title: "ignore_last_minute_schedule_change", description: "", checked_in: false, @@ -174,8 +176,8 @@ class StaffAPI < DriverSpecs::MockDriver booked_by_email: "user_one@example.com", booked_by_name: "User One", process_state: "approved", - last_changed: (TIME_UTC - 20.minutes).to_unix, - created: (TIME_UTC - 20.minutes).to_unix, + last_changed: (TIME_LOCAL - 20.minutes).to_unix, + created: (TIME_LOCAL - 20.minutes).to_unix, }, { id: 8, @@ -185,9 +187,9 @@ class StaffAPI < DriverSpecs::MockDriver asset_id: "desk_008", zones: ["zone-1234"], booking_type: "desk", - booking_start: (TIME_UTC - 2.minutes).to_unix, - booking_end: (TIME_UTC + 1.hour).to_unix, - timezone: "Australia/Darwin", + booking_start: (TIME_LOCAL - 2.minutes).to_unix, + booking_end: (TIME_LOCAL + 1.hour).to_unix, + timezone: TIMEZONE, title: "reject_on_start", description: "", checked_in: false, @@ -197,8 +199,8 @@ class StaffAPI < DriverSpecs::MockDriver booked_by_email: "user_one@example.com", booked_by_name: "User One", process_state: "approved", - last_changed: (TIME_UTC - 20.minutes).to_unix, - created: (TIME_UTC - 20.minutes).to_unix, + last_changed: (TIME_LOCAL - 20.minutes).to_unix, + created: (TIME_LOCAL - 20.minutes).to_unix, }, { id: 9, @@ -208,9 +210,9 @@ class StaffAPI < DriverSpecs::MockDriver asset_id: "desk_009", zones: ["zone-1234"], booking_type: "desk", - booking_start: (TIME_UTC - 11.minutes).to_unix, - booking_end: (TIME_UTC + 1.hour).to_unix, - timezone: "Australia/Darwin", + booking_start: (TIME_LOCAL - 11.minutes).to_unix, + booking_end: (TIME_LOCAL + 1.hour).to_unix, + timezone: TIMEZONE, title: "release_override_aol", description: "", checked_in: false, @@ -220,8 +222,8 @@ class StaffAPI < DriverSpecs::MockDriver booked_by_email: "user_three@example.com", booked_by_name: "User Three", process_state: "approved", - last_changed: (TIME_UTC - 20.minutes).to_unix, - created: (TIME_UTC - 20.minutes).to_unix, + last_changed: (TIME_LOCAL - 20.minutes).to_unix, + created: (TIME_LOCAL - 20.minutes).to_unix, }, { id: 10, @@ -231,9 +233,9 @@ class StaffAPI < DriverSpecs::MockDriver asset_id: "desk_010", zones: ["zone-1234"], booking_type: "desk", - booking_start: (TIME_UTC - 11.minutes).to_unix, - booking_end: (TIME_UTC + 1.hour).to_unix, - timezone: "Australia/Darwin", + booking_start: (TIME_LOCAL - 11.minutes).to_unix, + booking_end: (TIME_LOCAL + 1.hour).to_unix, + timezone: TIMEZONE, title: "ignore_override", description: "", checked_in: false, @@ -243,8 +245,8 @@ class StaffAPI < DriverSpecs::MockDriver booked_by_email: "user_four@example.com", booked_by_name: "User Four", process_state: "approved", - last_changed: (TIME_UTC - 20.minutes).to_unix, - created: (TIME_UTC - 20.minutes).to_unix, + last_changed: (TIME_LOCAL - 20.minutes).to_unix, + created: (TIME_LOCAL - 20.minutes).to_unix, }, { id: 11, @@ -254,9 +256,9 @@ class StaffAPI < DriverSpecs::MockDriver asset_id: "desk_011", zones: ["zone-1234"], booking_type: "desk", - booking_start: (TIME_UTC - 11.minutes).to_unix, - booking_end: (TIME_UTC + 1.hour).to_unix, - timezone: "Australia/Darwin", + booking_start: (TIME_LOCAL - 11.minutes).to_unix, + booking_end: (TIME_LOCAL + 1.hour).to_unix, + timezone: TIMEZONE, title: "ignore_checked_in", description: "", checked_in: true, @@ -266,8 +268,8 @@ class StaffAPI < DriverSpecs::MockDriver booked_by_email: "user_one@example.com", booked_by_name: "User One", process_state: "approved", - last_changed: (TIME_UTC - 20.minutes).to_unix, - created: (TIME_UTC - 20.minutes).to_unix, + last_changed: (TIME_LOCAL - 20.minutes).to_unix, + created: (TIME_LOCAL - 20.minutes).to_unix, }, { id: 12, @@ -277,9 +279,9 @@ class StaffAPI < DriverSpecs::MockDriver asset_id: "desk_012", zones: ["zone-1234"], booking_type: "desk", - booking_start: (TIME_UTC - 1.seconds).to_unix, - booking_end: (TIME_UTC + 1.hour).to_unix, - timezone: "Australia/Darwin", + booking_start: (TIME_LOCAL - 1.seconds).to_unix, + booking_end: (TIME_LOCAL + 1.hour).to_unix, + timezone: TIMEZONE, title: "ignore_created_after_start", description: "", checked_in: false, @@ -289,8 +291,8 @@ class StaffAPI < DriverSpecs::MockDriver booked_by_email: "user_one@example.com", booked_by_name: "User One", process_state: "approved", - last_changed: TIME_UTC.to_unix, - created: TIME_UTC.to_unix, + last_changed: TIME_LOCAL.to_unix, + created: TIME_LOCAL.to_unix, }, { id: 13, @@ -300,9 +302,9 @@ class StaffAPI < DriverSpecs::MockDriver asset_id: "desk_013", zones: ["zone-1234"], booking_type: "desk", - booking_start: TIME_UTC.to_unix, - booking_end: (TIME_UTC + 1.hour).to_unix, - timezone: "Australia/Darwin", + booking_start: TIME_LOCAL.to_unix, + booking_end: (TIME_LOCAL + 1.hour).to_unix, + timezone: TIMEZONE, title: "ignore_same_created_and_start", description: "", checked_in: false, @@ -312,8 +314,8 @@ class StaffAPI < DriverSpecs::MockDriver booked_by_email: "user_one@example.com", booked_by_name: "User One", process_state: "approved", - last_changed: TIME_UTC.to_unix, - created: TIME_UTC.to_unix, + last_changed: TIME_LOCAL.to_unix, + created: TIME_LOCAL.to_unix, }, { id: 14, @@ -323,9 +325,9 @@ class StaffAPI < DriverSpecs::MockDriver asset_id: "desk_014", zones: ["zone-1234"], booking_type: "desk", - booking_start: (TIME_UTC + 5.minutes).to_unix, - booking_end: (TIME_UTC + 1.hour).to_unix, - timezone: "Australia/Darwin", + booking_start: (TIME_LOCAL + 5.minutes).to_unix, + booking_end: (TIME_LOCAL + 1.hour).to_unix, + timezone: TIMEZONE, title: "notify_created_yesterday", description: "", checked_in: false, @@ -346,9 +348,9 @@ class StaffAPI < DriverSpecs::MockDriver asset_id: "desk_015", zones: ["zone-1234"], booking_type: "desk", - booking_start: (TIME_UTC - 11.minutes).to_unix, - booking_end: (TIME_UTC + 1.hour).to_unix, - timezone: "Australia/Darwin", + booking_start: (TIME_LOCAL - 11.minutes).to_unix, + booking_end: (TIME_LOCAL + 1.hour).to_unix, + timezone: TIMEZONE, title: "reject_created_yesterday", description: "", checked_in: false, @@ -369,9 +371,9 @@ class StaffAPI < DriverSpecs::MockDriver asset_id: "desk_016", zones: ["zone-1234"], booking_type: "desk", - booking_start: (TIME_UTC - 6.minutes).to_unix, - booking_end: (TIME_UTC + 1.hour).to_unix, - timezone: "Australia/Darwin", + booking_start: (TIME_LOCAL - 6.minutes).to_unix, + booking_end: (TIME_LOCAL + 1.hour).to_unix, + timezone: TIMEZONE, title: "notify_after_start", description: "", checked_in: false, @@ -381,8 +383,32 @@ class StaffAPI < DriverSpecs::MockDriver booked_by_email: "user_one@example.com", booked_by_name: "User One", process_state: "approved", - last_changed: (TIME_UTC - 20.minutes).to_unix, - created: (TIME_UTC - 20.minutes).to_unix, + last_changed: (TIME_LOCAL - 20.minutes).to_unix, + created: (TIME_LOCAL - 20.minutes).to_unix, + }, + { + id: 17, + user_id: "user-wfh", + user_email: "user_one@example.com", + user_name: "User One", + asset_id: "desk_017", + zones: ["zone-1234"], + booking_type: "desk", + booking_start: TIME_START_OF_DAY.to_unix, + booking_end: TIME_END_OF_DAY.to_unix, + timezone: TIMEZONE, + all_day: true, + title: "all_day", + description: "", + checked_in: false, + rejected: false, + approved: true, + booked_by_id: "user-wfh", + booked_by_email: "user_one@example.com", + booked_by_name: "User One", + process_state: "approved", + last_changed: (TIME_LOCAL - 2.days).to_unix, + created: (TIME_LOCAL - 2.days).to_unix, }, ] @@ -703,6 +729,9 @@ DriverSpecs.mock_driver "Place::AutoRelease" do Mailer: {Mailer}, }) + timezone = "Australia/Sydney" + time_local = Time.local(location: Time::Location.load(timezone)) + settings({ auto_release: { time_before: 10, @@ -716,7 +745,7 @@ DriverSpecs.mock_driver "Place::AutoRelease" do resp.not_nil!.as_h["id"].should eq "zone-1234" resp = exec(:get_pending_bookings).get - resp.not_nil!.as_a.size.should eq 15 + resp.not_nil!.as_a.size.should eq 16 resp = exec(:get_user_preferences?, "user-wfh").get resp.not_nil!.as_h.keys.should eq ["work_preferences", "work_overrides"] @@ -775,6 +804,33 @@ DriverSpecs.mock_driver "Place::AutoRelease" do "reject_created_yesterday", ] + # all_day_start: + ################ + + # all_day_start: 20 minutes in the future + all_day_start_in_20_minutes = (time_local + 20.minutes).hour + ((time_local + 20.minutes).minute / 60.0) + settings({ + time_window_hours: 8, + auto_release: { + time_before: 10, + time_after: 10, + resources: ["desk"], + }, + release_locations: ["wfh", "aol"], + skip_created_after_start: true, + skip_same_day: true, + all_day_start: all_day_start_in_20_minutes, + }) + + resp = exec(:pending_release).get + pending_release = resp.not_nil!.as_a.map(&.as_h["title"]) + pending_release.size.should eq 3 + pending_release.should eq [ + "notify_created_yesterday", + "reject_created_yesterday", + "all_day", + ] + ##################################### # End of tests for: #pending_release @@ -796,7 +852,6 @@ DriverSpecs.mock_driver "Place::AutoRelease" do # Ensure self[:pending_release] holds the correct bookings for the settings before testing #release_bookings resp = exec(:pending_release).get pending_release = resp.not_nil!.as_a.map(&.as_h["title"]) - pending_release.size.should eq 10 pending_release.should eq [ "ignore", "notify", @@ -842,6 +897,56 @@ DriverSpecs.mock_driver "Place::AutoRelease" do resp.should eq [3, 9, 15, 8, 16] system(:StaffAPI_1)[:rejected].should eq 5 + # all_day_start: + ################ + + # all_day_start: 20 minutes in the future + all_day_start_in_20_minutes = (time_local + 20.minutes).hour + ((time_local + 20.minutes).minute / 60.0) + settings({ + time_window_hours: 8, + auto_release: { + time_before: 10, + time_after: 10, + resources: ["desk"], + }, + release_locations: ["wfh", "aol"], + skip_created_after_start: true, + skip_same_day: true, + all_day_start: all_day_start_in_20_minutes, + }) + + # Ensure self[:pending_release] holds the correct bookings for the settings before testing #release_bookings + resp = exec(:pending_release).get + pending_release = resp.not_nil!.as_a.map(&.as_h["title"]) + pending_release.should eq [ + "notify_created_yesterday", + "reject_created_yesterday", + "all_day", + ] + + resp = exec(:release_bookings).get + resp.should eq [15] + system(:StaffAPI_1)[:rejected].should eq 5 + + # all_day_start: 20 minutes in the past + all_day_start_20_minutes_ago = (time_local - 20.minutes).hour + ((time_local - 20.minutes).minute / 60.0) + settings({ + time_window_hours: 8, + auto_release: { + time_before: 10, + time_after: 10, + resources: ["desk"], + }, + release_locations: ["wfh", "aol"], + skip_created_after_start: true, + skip_same_day: true, + all_day_start: all_day_start_20_minutes_ago, + }) + + resp = exec(:release_bookings).get + resp.should eq [15, 17] + system(:StaffAPI_1)[:rejected].should eq 6 + ##################################### # End of tests for: #release_bookings From 474b24639c0f0177cace499f5d91ac57e6febfcf Mon Sep 17 00:00:00 2001 From: Mia Bennett Date: Tue, 1 Apr 2025 14:39:08 +0930 Subject: [PATCH 10/15] feat(booking_model): [PPT-1903] add all_day to booking_model --- drivers/place/booking_model.cr | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/place/booking_model.cr b/drivers/place/booking_model.cr index 9635340f29f..14f6f0bb122 100644 --- a/drivers/place/booking_model.cr +++ b/drivers/place/booking_model.cr @@ -52,6 +52,8 @@ class Place::Booking property extension_data : Hash(String, JSON::Any) { {} of String => JSON::Any } getter recurrence_type : String? = nil + property all_day : Bool = false + def recurring? @recurrence_type != "none" end From f0bc55d66735e8b6cc7f6437a4a892dc64f021c1 Mon Sep 17 00:00:00 2001 From: Mia Bennett Date: Tue, 1 Apr 2025 14:40:20 +0930 Subject: [PATCH 11/15] test(auto_release): adjust #enabled? and #send_release_emails tests --- drivers/place/auto_release_spec.cr | 37 ++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/drivers/place/auto_release_spec.cr b/drivers/place/auto_release_spec.cr index effdb25f62b..ec004a00fcd 100644 --- a/drivers/place/auto_release_spec.cr +++ b/drivers/place/auto_release_spec.cr @@ -957,11 +957,35 @@ DriverSpecs.mock_driver "Place::AutoRelease" do time_window_hours: 8, auto_release: { time_before: 10, - time_after: 10, + time_after: 0, resources: ["desk"], }, + release_locations: ["wfh", "aol"], + skip_created_after_start: true, + skip_same_day: false, + skip_all_day: true, }) + # Ensure self[:pending_release] holds the correct bookings for the settings before testing #release_bookings + resp = exec(:pending_release).get + pending_release = resp.not_nil!.as_a.map(&.as_h["title"]) + pending_release.should eq [ + "ignore", + "notify", + "reject", + "ignore_last_minute_checkin", + "ignore_last_minute_schedule_change", + "reject_on_start", + "release_override_aol", + "notify_created_yesterday", + "reject_created_yesterday", + "notify_after_start", + ] + + # Ensure self[:released_booking_ids] holds the correct bookings for the settings before testing #send_release_emails + resp = exec(:release_bookings).get + resp.should eq [15, 3, 8, 9, 16] + # Send email once booking is past the time_before window, # but before the time_after window # (booking_id: 2, title: notify) @@ -1036,17 +1060,6 @@ DriverSpecs.mock_driver "Place::AutoRelease" do # Start of tests for: #enabled? ############################### - # disabled when both time_before and time_after are 0 - settings({ - auto_release: { - time_before: 0, - time_after: 0, - resources: ["desk"], - }, - }) - resp = exec(:enabled?).get - resp.should eq nil - # enabled when there are resources settings({ auto_release: { From aff1be0fd90b9d051598933d6767fdc46cdc9005 Mon Sep 17 00:00:00 2001 From: Mia Bennett Date: Tue, 1 Apr 2025 15:55:07 +0930 Subject: [PATCH 12/15] test(auto_release): [PPT-1900] release_outside_hours --- drivers/place/auto_release_spec.cr | 174 ++++++++++++++++++++++------- 1 file changed, 135 insertions(+), 39 deletions(-) diff --git a/drivers/place/auto_release_spec.cr b/drivers/place/auto_release_spec.cr index ec004a00fcd..4b6b5f83c43 100644 --- a/drivers/place/auto_release_spec.cr +++ b/drivers/place/auto_release_spec.cr @@ -15,6 +15,8 @@ class StaffAPI < DriverSpecs::MockDriver TIME_YESTERDAY = TIME_LOCAL - 1.day TIME_START_OF_DAY = TIME_LOCAL - TIME_LOCAL.hour.hours - TIME_LOCAL.minute.minutes - TIME_LOCAL.second.seconds TIME_END_OF_DAY = TIME_START_OF_DAY + 1.day - 1.seconds + DATE = TIME_LOCAL.to_s(format: "%F") + DAY_OF_WEEK = TIME_LOCAL.day_of_week.value == 0 ? 7 : TIME_LOCAL.day_of_week.value # Using a constant for bookings to ensure the times don't change during tests BOOKINGS = [ @@ -410,6 +412,30 @@ class StaffAPI < DriverSpecs::MockDriver last_changed: (TIME_LOCAL - 2.days).to_unix, created: (TIME_LOCAL - 2.days).to_unix, }, + { + id: 18, + user_id: "user-after-hours", + user_email: "user_five@example.com", + user_name: "User Five", + asset_id: "desk_018", + zones: ["zone-1234"], + booking_type: "desk", + booking_start: (TIME_LOCAL - 20.minutes).to_unix, + booking_end: (TIME_LOCAL + 40.minutes).to_unix, + timezone: TIMEZONE, + all_day: false, + title: "outside_hours", + description: "", + checked_in: false, + rejected: false, + approved: true, + booked_by_id: "user-after-hours", + booked_by_email: "user_five@example.com", + booked_by_name: "User Five", + process_state: "approved", + last_changed: TIME_YESTERDAY.to_unix, + created: TIME_YESTERDAY.to_unix, + }, ] NOW = Time.local(location: Time::Location.load(TIMEZONE)) @@ -467,25 +493,13 @@ class StaffAPI < DriverSpecs::MockDriver day_of_week: i, blocks: [ { - start_time: (Time.local(location: Time::Location.load("Australia/Sydney")) - 4.hours).hour, - end_time: (Time.local(location: Time::Location.load("Australia/Sydney")) + 4.hours).hour, + start_time: (TIME_LOCAL - 4.hours).hour, + end_time: (TIME_LOCAL + 4.hours).hour, location: "wfh", }, ], } end, - work_overrides: { - "2024-02-15": { - day_of_week: 4, - blocks: [ - { - start_time: 9, - end_time: 17, - location: "wfo", - }, - ], - }, - }, sys_admin: false, support: false, email: "user_one@example.com", @@ -515,25 +529,13 @@ class StaffAPI < DriverSpecs::MockDriver day_of_week: i, blocks: [ { - start_time: (Time.local(location: Time::Location.load("Australia/Sydney")) - 4.hours).hour, - end_time: (Time.local(location: Time::Location.load("Australia/Sydney")) + 4.hours).hour, + start_time: (TIME_LOCAL - 4.hours).hour, + end_time: (TIME_LOCAL + 4.hours).hour, location: "wfo", }, ], } end, - work_overrides: { - "2024-02-15": { - day_of_week: 4, - blocks: [ - { - start_time: 9, - end_time: 17, - location: "wfo", - }, - ], - }, - }, sys_admin: false, support: false, email: "user_two@example.com", @@ -563,8 +565,8 @@ class StaffAPI < DriverSpecs::MockDriver day_of_week: i, blocks: [ { - start_time: (Time.local(location: Time::Location.load("Australia/Sydney")) - 4.hours).hour, - end_time: (Time.local(location: Time::Location.load("Australia/Sydney")) + 4.hours).hour, + start_time: (TIME_LOCAL - 4.hours).hour, + end_time: (TIME_LOCAL + 4.hours).hour, location: "wfo", }, ], @@ -575,8 +577,8 @@ class StaffAPI < DriverSpecs::MockDriver day_of_week: DAY_OF_WEEK, blocks: [ { - start_time: (Time.local(location: Time::Location.load("Australia/Sydney")) - 4.hours).hour, - end_time: (Time.local(location: Time::Location.load("Australia/Sydney")) + 4.hours).hour, + start_time: (TIME_LOCAL - 4.hours).hour, + end_time: (TIME_LOCAL + 4.hours).hour, location: "aol", }, ], @@ -611,8 +613,8 @@ class StaffAPI < DriverSpecs::MockDriver day_of_week: i, blocks: [ { - start_time: (Time.local(location: Time::Location.load("Australia/Sydney")) - 4.hours).hour, - end_time: (Time.local(location: Time::Location.load("Australia/Sydney")) + 4.hours).hour, + start_time: (TIME_LOCAL - 4.hours).hour, + end_time: (TIME_LOCAL + 4.hours).hour, location: "wfh", }, ], @@ -623,8 +625,8 @@ class StaffAPI < DriverSpecs::MockDriver day_of_week: DAY_OF_WEEK, blocks: [ { - start_time: (Time.local(location: Time::Location.load("Australia/Sydney")) - 4.hours).hour, - end_time: (Time.local(location: Time::Location.load("Australia/Sydney")) + 4.hours).hour, + start_time: (TIME_LOCAL - 4.hours).hour, + end_time: (TIME_LOCAL + 4.hours).hour, location: "wfo", }, ], @@ -640,6 +642,41 @@ class StaffAPI < DriverSpecs::MockDriver card_number: "", } + user_after_hours = { + created_at: Time.utc.to_unix, + id: id, + email_digest: "not_real_digest", + name: "User Five", + first_name: "User", + last_name: "Five", + groups: [] of String, + country: "Australia", + building: "", + image: "", + authority_id: "authority-after-hours", + deleted: false, + department: "", + work_preferences: 7.times.map do |i| + { + day_of_week: i, + blocks: [ + { + start_time: (TIME_LOCAL - 6.hours).hour, + end_time: (TIME_LOCAL - 2.hours).hour, + location: "wfo", + }, + ], + } + end, + sys_admin: false, + support: false, + email: "user_five@example.com", + phone: "", + ui_theme: "light", + login_name: "", + staff_id: "", + card_number: "", + } case id when "user-wfh" JSON.parse(user_wfh.to_json) @@ -649,6 +686,8 @@ class StaffAPI < DriverSpecs::MockDriver JSON.parse(user_aol.to_json) when "user-wfh-override" JSON.parse(user_wfh_override.to_json) + when "user-after-hours" + JSON.parse(user_after_hours.to_json) else JSON.parse(user_wfh.to_json) end @@ -767,7 +806,6 @@ DriverSpecs.mock_driver "Place::AutoRelease" do resp = exec(:pending_release).get pending_release = resp.not_nil!.as_a.map(&.as_h["title"]) - pending_release.size.should eq 10 pending_release.should eq [ "ignore", "notify", @@ -798,7 +836,6 @@ DriverSpecs.mock_driver "Place::AutoRelease" do resp = exec(:pending_release).get pending_release = resp.not_nil!.as_a.map(&.as_h["title"]) - pending_release.size.should eq 2 pending_release.should eq [ "notify_created_yesterday", "reject_created_yesterday", @@ -819,18 +856,43 @@ DriverSpecs.mock_driver "Place::AutoRelease" do release_locations: ["wfh", "aol"], skip_created_after_start: true, skip_same_day: true, + skip_all_day: false, all_day_start: all_day_start_in_20_minutes, }) resp = exec(:pending_release).get pending_release = resp.not_nil!.as_a.map(&.as_h["title"]) - pending_release.size.should eq 3 pending_release.should eq [ "notify_created_yesterday", "reject_created_yesterday", "all_day", ] + # release_outside_hours: true + ############################# + + settings({ + time_window_hours: 8, + auto_release: { + time_before: 10, + time_after: 10, + resources: ["desk"], + }, + release_locations: ["wfh", "aol"], + skip_created_after_start: true, + skip_same_day: true, + skip_all_day: true, + release_outside_hours: true, + }) + + resp = exec(:pending_release).get + pending_release = resp.not_nil!.as_a.map(&.as_h["title"]) + pending_release.should eq [ + "notify_created_yesterday", + "reject_created_yesterday", + "outside_hours", + ] + ##################################### # End of tests for: #pending_release @@ -940,6 +1002,7 @@ DriverSpecs.mock_driver "Place::AutoRelease" do release_locations: ["wfh", "aol"], skip_created_after_start: true, skip_same_day: true, + skip_all_day: false, all_day_start: all_day_start_20_minutes_ago, }) @@ -947,6 +1010,39 @@ DriverSpecs.mock_driver "Place::AutoRelease" do resp.should eq [15, 17] system(:StaffAPI_1)[:rejected].should eq 6 + + # release_outside_hours: true + ############################# + + settings({ + time_window_hours: 8, + auto_release: { + time_before: 10, + time_after: 10, + resources: ["desk"], + }, + release_locations: ["wfh", "aol"], + skip_created_after_start: true, + skip_same_day: true, + skip_all_day: true, + release_outside_hours: true, + }) + + # Ensure self[:pending_release] holds the correct bookings for the settings before testing #release_bookings + resp = exec(:pending_release).get + pending_release = resp.not_nil!.as_a.map(&.as_h["title"]) + pending_release.should eq [ + "notify_created_yesterday", + "reject_created_yesterday", + "outside_hours", + ] + + resp = exec(:release_bookings).get + resp.should eq [15, 18] + system(:StaffAPI_1)[:rejected].should eq 7 + + + ##################################### # End of tests for: #release_bookings From e863f7337c3fb9f422aaf7e41f9efb7f19209741 Mon Sep 17 00:00:00 2001 From: Mia Bennett Date: Tue, 1 Apr 2025 16:16:33 +0930 Subject: [PATCH 13/15] feat(auto_release): [PPT-1901] default_work_preferences --- drivers/place/auto_release.cr | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/drivers/place/auto_release.cr b/drivers/place/auto_release.cr index 4975dd7a56e..5db0f9867ce 100644 --- a/drivers/place/auto_release.cr +++ b/drivers/place/auto_release.cr @@ -163,16 +163,14 @@ class Place::AutoRelease < PlaceOS::Driver @[Security(Level::Support)] def get_user_preferences?(user_id : String) - user = staff_api.user(user_id).get + user = User.from_json staff_api.user(user_id).get.to_json - work_preferences = Array(WorktimePreference).from_json user.as_h["work_preferences"].to_json + work_preferences = user.work_preferences work_preferences = @default_work_preferences if work_preferences.empty? - work_overrides = Hash(String, WorktimePreference).from_json user.as_h["work_overrides"].to_json - - {work_preferences: work_preferences, work_overrides: work_overrides} + {work_preferences: work_preferences, work_overrides: user.work_overrides} rescue - logger.warn { "unable to obtain work location for user #{user_id}" } + logger.debug { "unable to obtain work location for user #{user_id}" } nil end @@ -445,6 +443,13 @@ class Place::AutoRelease < PlaceOS::Driver end end + record User, + id : String, + work_preferences : Array(WorktimePreference) = [] of WorktimePreference, + work_overrides : Hash(String, WorktimePreference) = Hash(String, WorktimePreference).new do + include JSON::Serializable + end + # start_time: Start time of work hours. e.g. `7.5` being 7:30AM # end_time: End time of work hours. e.g. `18.5` being 6:30PM # location: Name of the location the work is being performed at From 0b7d188db4fd0e87030ef6e47dceaff9bd891331 Mon Sep 17 00:00:00 2001 From: Mia Bennett Date: Tue, 1 Apr 2025 16:17:13 +0930 Subject: [PATCH 14/15] test(auto_release): [PPT-1901] default_work_preferences --- drivers/place/auto_release_spec.cr | 130 ++++++++++++++++++++++++++--- 1 file changed, 118 insertions(+), 12 deletions(-) diff --git a/drivers/place/auto_release_spec.cr b/drivers/place/auto_release_spec.cr index 4b6b5f83c43..a093deb26be 100644 --- a/drivers/place/auto_release_spec.cr +++ b/drivers/place/auto_release_spec.cr @@ -15,8 +15,8 @@ class StaffAPI < DriverSpecs::MockDriver TIME_YESTERDAY = TIME_LOCAL - 1.day TIME_START_OF_DAY = TIME_LOCAL - TIME_LOCAL.hour.hours - TIME_LOCAL.minute.minutes - TIME_LOCAL.second.seconds TIME_END_OF_DAY = TIME_START_OF_DAY + 1.day - 1.seconds - DATE = TIME_LOCAL.to_s(format: "%F") - DAY_OF_WEEK = TIME_LOCAL.day_of_week.value == 0 ? 7 : TIME_LOCAL.day_of_week.value + DATE = TIME_LOCAL.to_s(format: "%F") + DAY_OF_WEEK = TIME_LOCAL.day_of_week.value == 0 ? 7 : TIME_LOCAL.day_of_week.value # Using a constant for bookings to ensure the times don't change during tests BOOKINGS = [ @@ -436,12 +436,32 @@ class StaffAPI < DriverSpecs::MockDriver last_changed: TIME_YESTERDAY.to_unix, created: TIME_YESTERDAY.to_unix, }, + { + id: 19, + user_id: "user-no-preferences", + user_email: "user_six@example.com", + user_name: "User Six", + asset_id: "desk_019", + zones: ["zone-1234"], + booking_type: "desk", + booking_start: (TIME_LOCAL + 5.minutes).to_unix, + booking_end: (TIME_LOCAL + 25.minutes).to_unix, + timezone: TIMEZONE, + all_day: false, + title: "no_preferences", + description: "", + checked_in: false, + rejected: false, + approved: true, + booked_by_id: "user-no-preferences", + booked_by_email: "user_six@example.com", + booked_by_name: "User Six", + process_state: "approved", + last_changed: TIME_YESTERDAY.to_unix, + created: TIME_YESTERDAY.to_unix, + }, ] - NOW = Time.local(location: Time::Location.load(TIMEZONE)) - DATE = NOW.to_s(format: "%F") - DAY_OF_WEEK = NOW.day_of_week.value == 0 ? 7 : NOW.day_of_week.value - def query_bookings( type : String? = nil, period_start : Int64? = nil, @@ -677,6 +697,31 @@ class StaffAPI < DriverSpecs::MockDriver staff_id: "", card_number: "", } + + user_no_preferences = { + created_at: Time.utc.to_unix, + id: id, + email_digest: "not_real_digest", + name: "User Six", + first_name: "User", + last_name: "Six", + groups: [] of String, + country: "Australia", + building: "", + image: "", + authority_id: "authority-no-preferences", + deleted: false, + department: "", + sys_admin: false, + support: false, + email: "user_six@example.com", + phone: "", + ui_theme: "light", + login_name: "", + staff_id: "", + card_number: "", + } + case id when "user-wfh" JSON.parse(user_wfh.to_json) @@ -688,6 +733,8 @@ class StaffAPI < DriverSpecs::MockDriver JSON.parse(user_wfh_override.to_json) when "user-after-hours" JSON.parse(user_after_hours.to_json) + when "user-no-preferences" + JSON.parse(user_no_preferences.to_json) else JSON.parse(user_wfh.to_json) end @@ -784,7 +831,7 @@ DriverSpecs.mock_driver "Place::AutoRelease" do resp.not_nil!.as_h["id"].should eq "zone-1234" resp = exec(:get_pending_bookings).get - resp.not_nil!.as_a.size.should eq 16 + resp.not_nil!.as_a.size.should eq 18 resp = exec(:get_user_preferences?, "user-wfh").get resp.not_nil!.as_h.keys.should eq ["work_preferences", "work_overrides"] @@ -882,7 +929,7 @@ DriverSpecs.mock_driver "Place::AutoRelease" do skip_created_after_start: true, skip_same_day: true, skip_all_day: true, - release_outside_hours: true, + release_outside_hours: true, }) resp = exec(:pending_release).get @@ -891,6 +938,66 @@ DriverSpecs.mock_driver "Place::AutoRelease" do "notify_created_yesterday", "reject_created_yesterday", "outside_hours", + "no_preferences", + ] + + # when release_outside_hours is false (the default) + settings({ + time_window_hours: 8, + auto_release: { + time_before: 10, + time_after: 10, + resources: ["desk"], + }, + release_locations: ["wfh", "aol"], + skip_created_after_start: true, + skip_same_day: true, + skip_all_day: true, + release_outside_hours: false, + }) + + resp = exec(:pending_release).get + pending_release = resp.not_nil!.as_a.map(&.as_h["title"]) + pending_release.should eq [ + "notify_created_yesterday", + "reject_created_yesterday", + ] + + # default_work_preferences + ########################## + + settings({ + time_window_hours: 8, + auto_release: { + time_before: 10, + time_after: 10, + resources: ["desk"], + }, + release_locations: ["wfh", "aol"], + skip_created_after_start: true, + skip_same_day: true, + skip_all_day: true, + release_outside_hours: false, + default_work_preferences: 7.times.map do |i| + { + day_of_week: i, + blocks: [ + { + start_time: (time_local - 4.hours).hour, + end_time: (time_local + 4.hours).hour, + location: "wfh", + }, + ], + } + end, + }) + + resp = exec(:pending_release).get + pending_release = resp.not_nil!.as_a.map(&.as_h["title"]) + pending_release.should eq [ + "notify_created_yesterday", + "reject_created_yesterday", + "no_preferences", ] ##################################### @@ -974,6 +1081,7 @@ DriverSpecs.mock_driver "Place::AutoRelease" do release_locations: ["wfh", "aol"], skip_created_after_start: true, skip_same_day: true, + skip_all_day: false, all_day_start: all_day_start_in_20_minutes, }) @@ -1010,7 +1118,6 @@ DriverSpecs.mock_driver "Place::AutoRelease" do resp.should eq [15, 17] system(:StaffAPI_1)[:rejected].should eq 6 - # release_outside_hours: true ############################# @@ -1025,7 +1132,7 @@ DriverSpecs.mock_driver "Place::AutoRelease" do skip_created_after_start: true, skip_same_day: true, skip_all_day: true, - release_outside_hours: true, + release_outside_hours: true, }) # Ensure self[:pending_release] holds the correct bookings for the settings before testing #release_bookings @@ -1035,14 +1142,13 @@ DriverSpecs.mock_driver "Place::AutoRelease" do "notify_created_yesterday", "reject_created_yesterday", "outside_hours", + "no_preferences", ] resp = exec(:release_bookings).get resp.should eq [15, 18] system(:StaffAPI_1)[:rejected].should eq 7 - - ##################################### # End of tests for: #release_bookings From 7bbe705de40aba98d110c261a8ab12066226d2f2 Mon Sep 17 00:00:00 2001 From: Mia Bennett Date: Wed, 2 Apr 2025 10:32:29 +0930 Subject: [PATCH 15/15] refactor(auto_release): move default_work_preferences, release_outside_hours, all_day_start to AutoReleaseConfig for management by Concierge --- drivers/place/auto_release.cr | 36 ++++++------- drivers/place/auto_release_spec.cr | 82 +++++++++++++++--------------- 2 files changed, 57 insertions(+), 61 deletions(-) diff --git a/drivers/place/auto_release.cr b/drivers/place/auto_release.cr index 5db0f9867ce..ac93b45d4e9 100644 --- a/drivers/place/auto_release.cr +++ b/drivers/place/auto_release.cr @@ -26,13 +26,10 @@ class Place::AutoRelease < PlaceOS::Driver # - wfh: Work From Home # - aol: Away on Leave # - wfo: Work From Office - skip_created_after_start: true, # Skip bookings created after the start time - skip_same_day: false, # Skip bookings created on the same day as the booking - skip_all_day: false, # Skip all day bookings - default_work_preferences: [] of WorktimePreference, # Default work preferences for users - release_outside_hours: false, # Release bookings outside of work hours - all_day_start: 8.0, # Start time used for all day bookings - asset_cache_timeout: 3600_i64, # 1 hour + skip_created_after_start: true, # Skip bookings created after the start time + skip_same_day: false, # Skip bookings created on the same day as the booking + skip_all_day: false, # Skip all day bookings + asset_cache_timeout: 3600_i64, # 1 hour }) accessor staff_api : StaffAPI_1 @@ -71,9 +68,6 @@ class Place::AutoRelease < PlaceOS::Driver @skip_created_after_start : Bool = true @skip_same_day : Bool = true @skip_all_day : Bool = false - @default_work_preferences : Array(WorktimePreference) = [] of WorktimePreference - @release_outside_hours : Bool = false - @all_day_start : Float64 = 8.0 def on_update @building_zone = nil @@ -95,9 +89,6 @@ class Place::AutoRelease < PlaceOS::Driver @skip_created_after_start = setting?(Bool, :skip_created_after_start) || true @skip_same_day = setting?(Bool, :skip_same_day) || false @skip_all_day = setting?(Bool, :skip_all_day) || false - @default_work_preferences = setting?(Array(WorktimePreference), :default_work_preferences) || [] of WorktimePreference - @release_outside_hours = setting?(Bool, :release_outside_hours) || false - @all_day_start = setting?(Float64, :all_day_start) || 8.0 @asset_cache_timeout = setting?(Int64, :asset_cache_timeout) || 3600_i64 clear_asset_cache @@ -166,7 +157,7 @@ class Place::AutoRelease < PlaceOS::Driver user = User.from_json staff_api.user(user_id).get.to_json work_preferences = user.work_preferences - work_preferences = @default_work_preferences if work_preferences.empty? + work_preferences = @auto_release.default_work_preferences if work_preferences.empty? {work_preferences: work_preferences, work_overrides: user.work_overrides} rescue @@ -221,7 +212,7 @@ class Place::AutoRelease < PlaceOS::Driver event_time = booking_start.hour + (booking_start.minute / 60.0) # use all_day_start for all day bookings - event_time = @all_day_start if booking.all_day + event_time = @auto_release.all_day_start if booking.all_day # exclude overrides with empty time blocks overrides = preferences[:work_overrides].select { |_, pref| pref.blocks.size > 0 } @@ -234,7 +225,7 @@ class Place::AutoRelease < PlaceOS::Driver elsif (preference = preferences[:work_preferences].find { |pref| pref.day_of_week == day_of_week }) && in_preference?(preference, event_time, @release_locations) results << booking - elsif @release_outside_hours + elsif @auto_release.release_outside_hours results << booking end end @@ -297,8 +288,8 @@ class Place::AutoRelease < PlaceOS::Driver private def all_day_start_time(booking_start : Time) : Time # Convert float hours/minutes to time # e.g. 7.5 = 7:30AM in the specified timezone - hours = @all_day_start.to_i - minutes = ((@all_day_start - hours) * 60).to_i + hours = @auto_release.all_day_start.to_i + minutes = ((@auto_release.all_day_start - hours) * 60).to_i time_in_zone = Time.local(booking_start.year, booking_start.month, booking_start.day, hours, minutes, location: building_zone.time_location!) end @@ -419,9 +410,12 @@ class Place::AutoRelease < PlaceOS::Driver include JSON::Serializable include JSON::Serializable::Unmapped - getter time_before : Int64 = 0 # minutes - getter time_after : Int64 = 0 # minutes - getter resources : Array(String) = [] of String + getter time_before : Int64 = 0 # Notification time before booking start in minutes + getter time_after : Int64 = 0 # Release time after booking start in minutes + getter resources : Array(String) = [] of String # Resources to release bookings for + getter default_work_preferences : Array(WorktimePreference) = [] of WorktimePreference # Default work preferences for users + getter release_outside_hours : Bool = false # Release bookings outside of work hours + getter all_day_start : Float64 = 8.0 # Start time used for all day bookings def initialize end diff --git a/drivers/place/auto_release_spec.cr b/drivers/place/auto_release_spec.cr index a093deb26be..666b4353b6f 100644 --- a/drivers/place/auto_release_spec.cr +++ b/drivers/place/auto_release_spec.cr @@ -849,6 +849,7 @@ DriverSpecs.mock_driver "Place::AutoRelease" do release_locations: ["wfh", "aol"], skip_created_after_start: true, skip_same_day: false, + skip_all_day: true, }) resp = exec(:pending_release).get @@ -879,6 +880,7 @@ DriverSpecs.mock_driver "Place::AutoRelease" do release_locations: ["wfh", "aol"], skip_created_after_start: true, skip_same_day: true, + skip_all_day: true, }) resp = exec(:pending_release).get @@ -896,15 +898,15 @@ DriverSpecs.mock_driver "Place::AutoRelease" do settings({ time_window_hours: 8, auto_release: { - time_before: 10, - time_after: 10, - resources: ["desk"], + time_before: 10, + time_after: 10, + resources: ["desk"], + all_day_start: all_day_start_in_20_minutes, }, release_locations: ["wfh", "aol"], skip_created_after_start: true, skip_same_day: true, skip_all_day: false, - all_day_start: all_day_start_in_20_minutes, }) resp = exec(:pending_release).get @@ -921,15 +923,15 @@ DriverSpecs.mock_driver "Place::AutoRelease" do settings({ time_window_hours: 8, auto_release: { - time_before: 10, - time_after: 10, - resources: ["desk"], + time_before: 10, + time_after: 10, + resources: ["desk"], + release_outside_hours: true, }, release_locations: ["wfh", "aol"], skip_created_after_start: true, skip_same_day: true, skip_all_day: true, - release_outside_hours: true, }) resp = exec(:pending_release).get @@ -945,15 +947,15 @@ DriverSpecs.mock_driver "Place::AutoRelease" do settings({ time_window_hours: 8, auto_release: { - time_before: 10, - time_after: 10, - resources: ["desk"], + time_before: 10, + time_after: 10, + resources: ["desk"], + release_outside_hours: false, }, release_locations: ["wfh", "aol"], skip_created_after_start: true, skip_same_day: true, skip_all_day: true, - release_outside_hours: false, }) resp = exec(:pending_release).get @@ -969,27 +971,27 @@ DriverSpecs.mock_driver "Place::AutoRelease" do settings({ time_window_hours: 8, auto_release: { - time_before: 10, - time_after: 10, - resources: ["desk"], + time_before: 10, + time_after: 10, + resources: ["desk"], + release_outside_hours: false, + default_work_preferences: 7.times.map do |i| + { + day_of_week: i, + blocks: [ + { + start_time: (time_local - 4.hours).hour, + end_time: (time_local + 4.hours).hour, + location: "wfh", + }, + ], + } + end, }, release_locations: ["wfh", "aol"], skip_created_after_start: true, skip_same_day: true, skip_all_day: true, - release_outside_hours: false, - default_work_preferences: 7.times.map do |i| - { - day_of_week: i, - blocks: [ - { - start_time: (time_local - 4.hours).hour, - end_time: (time_local + 4.hours).hour, - location: "wfh", - }, - ], - } - end, }) resp = exec(:pending_release).get @@ -1074,15 +1076,15 @@ DriverSpecs.mock_driver "Place::AutoRelease" do settings({ time_window_hours: 8, auto_release: { - time_before: 10, - time_after: 10, - resources: ["desk"], + time_before: 10, + time_after: 10, + resources: ["desk"], + all_day_start: all_day_start_in_20_minutes, }, release_locations: ["wfh", "aol"], skip_created_after_start: true, skip_same_day: true, skip_all_day: false, - all_day_start: all_day_start_in_20_minutes, }) # Ensure self[:pending_release] holds the correct bookings for the settings before testing #release_bookings @@ -1103,15 +1105,15 @@ DriverSpecs.mock_driver "Place::AutoRelease" do settings({ time_window_hours: 8, auto_release: { - time_before: 10, - time_after: 10, - resources: ["desk"], + time_before: 10, + time_after: 10, + resources: ["desk"], + all_day_start: all_day_start_20_minutes_ago, }, release_locations: ["wfh", "aol"], skip_created_after_start: true, skip_same_day: true, skip_all_day: false, - all_day_start: all_day_start_20_minutes_ago, }) resp = exec(:release_bookings).get @@ -1124,15 +1126,15 @@ DriverSpecs.mock_driver "Place::AutoRelease" do settings({ time_window_hours: 8, auto_release: { - time_before: 10, - time_after: 10, - resources: ["desk"], + time_before: 10, + time_after: 10, + resources: ["desk"], + release_outside_hours: true, }, release_locations: ["wfh", "aol"], skip_created_after_start: true, skip_same_day: true, skip_all_day: true, - release_outside_hours: true, }) # Ensure self[:pending_release] holds the correct bookings for the settings before testing #release_bookings