Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
58bb971
feat(staff_api): paginate assets and add update_asset
stakach Nov 1, 2026
ed1a182
feat(custom_bookings_sync): read desks via Place::DeskSource
stakach Nov 1, 2026
867a5e2
feat(mobile_checkin_logic): read desks via Place::DeskSource
stakach Nov 1, 2026
d6e55d8
refactor(desk_source): share Desk#value for metadata key lookups
stakach Nov 1, 2026
d5a6f05
feat(desk_bookings_locations): read desks via Place::DeskSource
stakach Nov 1, 2026
750819d
feat(booking_approver): read desks via Place::DeskSource
stakach Nov 1, 2026
b0fa6a1
feat(booking_approval_workflows): read desks via Place::DeskSource
stakach Nov 1, 2026
7eb9842
feat(area_management): read desks via Place::DeskSource
stakach Nov 1, 2026
de19ee6
feat(desk_control): read desks via Place::DeskSource
stakach Nov 1, 2026
70ab17e
feat(desk_allocations): read desks via Place::DeskSource
stakach Nov 1, 2026
1d660c3
fix(desk/allocations): keep original desk ids for the desk management…
stakach Nov 1, 2026
69a5987
feat(grant_area_access): read desks via Place::DeskSource
stakach Nov 1, 2026
68b9c7f
feat(orbility_approvals): read desks via Place::DeskSource
stakach Nov 1, 2026
b3b5659
feat(gobright_location_service): read desks via Place::DeskSource
stakach Nov 1, 2026
3a49bbf
feat(asset_name_resolver): read desks via Place::DeskSource
stakach Nov 1, 2026
ce21581
feat(llm/workplace): read desks via Place::DeskSource
stakach Nov 1, 2026
514248c
feat(llm/campus): read desks via Place::DeskSource
stakach Nov 1, 2026
aa08157
style: lint desk asset specs
stakach Nov 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 15 additions & 60 deletions drivers/floorsense/custom_bookings_sync.cr
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ require "json"
require "oauth2"
require "./models"
require "placeos"
require "../place/desk_source"

class Floorsense::CustomBookingsSync < PlaceOS::Driver
descriptive_name "Floorsense Desk Bookings Sync"
Expand All @@ -20,6 +21,8 @@ class Floorsense::CustomBookingsSync < PlaceOS::Driver
bind Floorsense_1, :event_50, :booking_released
bind Floorsense_1, :event_53, :booking_confirmed

include Place::DeskSource

alias PlaceUser = PlaceOS::Client::API::Models::User

DESK_SOURCE_EXTENSION_DATA_MODIFICATION = "floorsense"
Expand All @@ -46,6 +49,9 @@ class Floorsense::CustomBookingsSync < PlaceOS::Driver
"features" => "deskAttributes",
},
meta_ext_static: {} of String => String,

# read desks from `_DESKS_` assets instead of the `desks` zone metadata
use_desk_assets: false,
})

@user_ids_cache : Hash(String, String) = {} of String => String
Expand Down Expand Up @@ -280,33 +286,26 @@ class Floorsense::CustomBookingsSync < PlaceOS::Driver
desk_lookup = {} of String => DeskMeta

begin
metadata = staff_api.metadata(
zone_id,
"desks"
).get["desks"]["details"].as_a

lookup_key = @floorsense_lookup_key
metadata.each do |desk|
desk = desk.as_h
place_id = desk["id"]?.try(&.as_s.presence)
next unless place_id

floor_id = desk[lookup_key]?.try(&.as_s.presence)
level_desks(zone_id).each do |desk|
floor_id = desk.value(@floorsense_lookup_key).try(&.as_s?).presence
next unless floor_id

# Additional data for adhoc bookings
ext_data = {
"floorsense_id" => JSON::Any.new(floor_id),
}
title = desk["name"]?.try(&.as_s) || place_id
@meta_ext_mappings.each do |meta_key, ext_key|
if value = desk[meta_key]?
if value = desk.value(meta_key)
ext_data[ext_key] = value
end
end

ids = DeskMeta.new(place_id, floor_id, building_id, title, ext_data)
desk_lookup[place_id] = ids
@meta_ext_static.each do |key, value|
ext_data[key] = JSON::Any.new(value)
end

ids = DeskMeta.new(desk.id, floor_id, building_id, desk.name, ext_data)
desk_lookup[desk.id] = ids
desk_lookup[floor_id] = ids
end
desk_lookup
Expand Down Expand Up @@ -921,48 +920,4 @@ class Floorsense::CustomBookingsSync < PlaceOS::Driver
logger.warn(exception: error) { "desk #{desk_id} not found" }
raise "Desk not found"
end

def placeos_desk_metadata(zone_id : String, building_id : String?)
desk_lookup = {} of String => DeskMeta

begin
metadata = staff_api.metadata(
zone_id,
"desks"
).get["desks"]["details"].as_a

lookup_key = @floorsense_lookup_key
metadata.each do |desk|
desk = desk.as_h
place_id = desk["id"]?.try(&.as_s.presence)
next unless place_id

floor_id = desk[lookup_key]?.try(&.as_s.presence)
next unless floor_id

# Additional data for adhoc bookings
ext_data = {
"floorsense_id" => JSON::Any.new(floor_id),
}
title = desk["name"]?.try(&.as_s) || place_id
@meta_ext_mappings.each do |meta_key, ext_key|
if value = desk[meta_key]?
ext_data[ext_key] = value
end
end

@meta_ext_static.each do |key, value|
ext_data[key] = JSON::Any.new(value)
end

ids = DeskMeta.new(place_id, floor_id, building_id, title, ext_data)
desk_lookup[place_id] = ids
desk_lookup[floor_id] = ids
end
desk_lookup
rescue error
logger.warn(exception: error) { "unable to obtain desk metadata for #{zone_id}" }
desk_lookup
end
end
end
159 changes: 159 additions & 0 deletions drivers/floorsense/custom_bookings_sync_spec.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,163 @@
require "placeos-driver/spec"
require "./models"

class StaffAPIMock < DriverSpecs::MockDriver
def metadata(id : String, key : String? = nil)
JSON.parse({
desks: {
name: "desks",
parent_id: id,
details: [
{id: "desk-1", name: "Desk 1", floorsense_id: "fs-1", neighbourhoodID: "north", features: ["standing"]},
{id: "desk-2", name: "Desk 2", floorsense_id: "fs-2"},
# no floorsense id, not synced
{id: "desk-3", name: "Desk 3"},
],
},
}.to_json)
end

def asset_types(category_id : String? = nil, zone_id : String? = nil, brand : String? = nil, model_number : String? = nil)
JSON.parse([{id: "type-desk", name: "_DESKS_"}].to_json)
end

def assets(
type_id : String? = nil,
zone_id : String? = nil,
order_id : String? = nil,
barcode : String? = nil,
serial_number : String? = nil,
bookable : Bool? = nil,
accessible : Bool? = nil,
features : Array(String)? = nil,
zones : Array(String)? = nil,
limit : Int32 = 1000,
next_page : String? = nil,
)
return JSON.parse("[]") unless type_id == "type-desk" && zone_id == "zone-level"

if next_page == "page-2"
JSON.parse([
{id: "asset-2", identifier: "desk-2", map_id: "desk-2", zone_id: zone_id, other_data: {floorsense_id: "fs-2"}},
{id: "asset-3", identifier: "desk-3", map_id: "desk-3", zone_id: zone_id, other_data: {} of String => String},
].to_json)
else
JSON.parse([
{
id: "asset-1", identifier: "desk-1", map_id: "desk-1", zone_id: zone_id, next_page: "page-2",
features: ["standing"], other_data: {floorsense_id: "fs-1", neighbourhoodID: "north"},
},
].to_json)
end
end

# background floor syncs
def query_bookings(
type : String? = nil,
period_start : Int64? = nil,
period_end : Int64? = nil,
zones : Array(String) = [] of String,
user : String? = nil,
email : String? = nil,
state : String? = nil,
event_id : String? = nil,
ical_uid : String? = nil,
created_before : Int64? = nil,
created_after : Int64? = nil,
approved : Bool? = nil,
rejected : Bool? = nil,
checked_in : Bool? = nil,
include_checked_out : Bool? = nil,
extension_data : JSON::Any? = nil,
deleted : Bool? = nil,
asset_id : String? = nil,
limit : Int32? = nil,
)
JSON.parse("[]")
end
end

DriverSpecs.mock_driver "Floorsense::CustomBookingsSync" do
system({
StaffAPI: {StaffAPIMock},
})

# `settings` replaces all settings
base_settings = {
floor_mappings: {
"plan-1": {
building_id: "zone-building",
level_id: "zone-level",
},
},
floorsense_lookup_key: "floorsense_id",
meta_ext_mappings: {
"neighbourhoodID" => "neighbourhood",
"features" => "deskAttributes",
},
meta_ext_static: {"source" => "placeos"},
use_desk_assets: false,
}
settings(base_settings)

it "should map desk metadata to floorsense desks" do
lookup = Hash(String, Floorsense::DeskMeta).from_json exec(:placeos_desk_metadata, "zone-level", "zone-building").get.to_json

# desk-3 has no floorsense id
lookup.keys.sort!.should eq ["desk-1", "desk-2", "fs-1", "fs-2"]

desk = lookup["desk-1"]
lookup["fs-1"].place_id.should eq "desk-1"
desk.place_id.should eq "desk-1"
desk.floor_id.should eq "fs-1"
desk.building.should eq "zone-building"
desk.title.should eq "Desk 1"
desk.ext_data.should eq({
"floorsense_id" => JSON::Any.new("fs-1"),
"neighbourhood" => JSON::Any.new("north"),
"deskAttributes" => JSON.parse(%(["standing"])),
"source" => JSON::Any.new("placeos"),
})

# desks without features or a neighbourhood don't map them
lookup["desk-2"].ext_data.should eq({
"floorsense_id" => JSON::Any.new("fs-2"),
"source" => JSON::Any.new("placeos"),
})
end

it "should map desk assets to floorsense desks" do
settings(base_settings.merge({use_desk_assets: true}))

lookup = Hash(String, Floorsense::DeskMeta).from_json exec(:placeos_desk_metadata, "zone-level", "zone-building").get.to_json

# assets across both pages, asset-3 has no floorsense id
lookup.keys.sort!.should eq ["asset-1", "asset-2", "fs-1", "fs-2"]

desk = lookup["fs-1"]
desk.place_id.should eq "asset-1"
desk.floor_id.should eq "fs-1"
desk.building.should eq "zone-building"
desk.title.should eq "desk-1"
desk.ext_data.should eq({
"floorsense_id" => JSON::Any.new("fs-1"),
"neighbourhood" => JSON::Any.new("north"),
"deskAttributes" => JSON.parse(%(["standing"])),
"source" => JSON::Any.new("placeos"),
})
lookup["fs-2"].place_id.should eq "asset-2"
lookup["fs-2"].ext_data.should eq({
"floorsense_id" => JSON::Any.new("fs-2"),
"source" => JSON::Any.new("placeos"),
})
end

it "should match the desk lookup key against desk fields" do
settings(base_settings.merge({use_desk_assets: true, floorsense_lookup_key: "map_id"}))

lookup = Hash(String, Floorsense::DeskMeta).from_json exec(:placeos_desk_metadata, "zone-level", "zone-building").get.to_json
lookup.keys.sort!.should eq ["asset-1", "asset-2", "asset-3", "desk-1", "desk-2", "desk-3"]
lookup["desk-3"].place_id.should eq "asset-3"
lookup["desk-3"].ext_data["floorsense_id"].should eq JSON::Any.new("desk-3")
end
end
31 changes: 12 additions & 19 deletions drivers/floorsense/mobile_checkin_logic.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require "placeos-driver"
require "../place/desk_source"

class Floorsense::MobileCheckinLogic < PlaceOS::Driver
descriptive_name "Floorsense Mobile Checkin Logic"
Expand All @@ -8,13 +9,18 @@ class Floorsense::MobileCheckinLogic < PlaceOS::Driver
accessor booking_sync : FloorsenseBookingSync_1
accessor staff_api : StaffAPI_1

include Place::DeskSource

default_settings({
time_zone: "Australia/Sydney",
booking_period: 120,
meta_ext_mappings: {
"neighbourhoodID" => "neighbourhood",
"features" => "deskAttributes",
},

# read desks from `_DESKS_` assets instead of the `desks` zone metadata
use_desk_assets: false,
})

@time_zone : Time::Location = Time::Location.load("Australia/Sydney")
Expand Down Expand Up @@ -89,16 +95,13 @@ class Floorsense::MobileCheckinLogic < PlaceOS::Driver
ext_data = {} of String => JSON::Any
begin
logger.debug { "obtaining metadata for desk #{place_desk} on level #{level_zone}" }
if desk_details = placeos_desk_metadata(level_zone, place_desk)
# check if the desk is bookable
if bookable = desk_details["bookable"]?
return "forbidden" if (bookable.as_s?.try(&.upcase) == "FALSE") || (bookable.as_bool? == false)
end
if desk = placeos_desk_metadata(level_zone, place_desk)
return "forbidden" unless desk.bookable?

title = desk_details["name"]?.try(&.as_s) || place_desk
title = desk.name

@meta_ext_mappings.each do |meta_key, ext_key|
if value = desk_details[meta_key]?
if value = desk.value(meta_key)
ext_data[ext_key] = value
end
end
Expand Down Expand Up @@ -130,17 +133,7 @@ class Floorsense::MobileCheckinLogic < PlaceOS::Driver
end
end

def placeos_desk_metadata(zone_id : String, asset_id : String)
metadata = staff_api.metadata(
zone_id,
"desks"
).get["desks"]["details"].as_a

metadata.each do |desk|
place_id = desk["id"]?.try(&.as_s)
next unless place_id
return desk.as_h if place_id == asset_id
end
nil
def placeos_desk_metadata(zone_id : String, asset_id : String) : Desk?
level_desks(zone_id).find { |desk| desk.id == asset_id }
end
end
Loading
Loading