operto 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 53adc7fd65b6257d17d1e93c2c2123134ac92189bf176d98fb89fb50e364a1ee
4
- data.tar.gz: 979c8c81475d4a39ff1a12c695b7a5da98de80d5e9260a9f534b9c9faf940481
3
+ metadata.gz: 31bc34a291dadd87aded5937654fc9824be5cc365e94010724ef763b75169b49
4
+ data.tar.gz: 46b42729bb8e8590b02452b56c83fe4387638e5db2bee5b290f6f98040196d63
5
5
  SHA512:
6
- metadata.gz: c61aa111e25800de6e098490e30a181c202be74cc8b0758c1099f147ea306575f4d7e5b5c7d30529404f3271322fe00dad1768e2b2762d2916de733a15959ab6
7
- data.tar.gz: 0b09cc302afb111a5dad1120eb55ba19cd9ce7220e3ace9432b6641b1a04293559a7dabddb6a3adc5e54c999b024e07cc27757229b8945a959a2206d50d98421
6
+ metadata.gz: 0cc0aa3b12cb92f0d521dfa5f46e6a834a69ddb047a881f9eb125a12a829bc5ff4ca2b9210a8c18b48f9df761abc81ad7374133fd2bac236dac55d6199cae374
7
+ data.tar.gz: 8579d576182fc7598faf1bc8a4fa5e7e166a995def47059412a00a74ab23f5854225ea702468611f4116a76a29db45ab6469a73182437b1e02b5222c449a27fb
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0 - 2026-09-27
4
+
5
+ - `Tasks::Index` returns the whole task: rule, booking, name, description, start and due times, and the active and completion flags.
6
+ - Add `Tasks::Show` for one task. A missing task fails with `Operto::NotFoundError`.
7
+ - `Tasks::Update` sends only the attributes it gets, so a description-only update keeps the name, schedule and rule.
8
+
3
9
  ## 0.2.0 - 2026-08-17
4
10
 
5
11
  - Add RBS type signatures for the public API.
data/README.md CHANGED
@@ -9,7 +9,7 @@ framework dependency.
9
9
 
10
10
  ```ruby
11
11
  # Gemfile
12
- gem 'operto', '~> 0.2.0'
12
+ gem 'operto', '~> 0.3.0'
13
13
  ```
14
14
 
15
15
  ```sh
@@ -66,7 +66,7 @@ Operto::Tasks::Create.new.call(
66
66
  `#call` returns a `Result`; `#call!` unwraps it and raises the underlying
67
67
  failure. Operations are grouped by resource:
68
68
 
69
- - `Operto::Tasks::{Create,Update,Index}`
69
+ - `Operto::Tasks::{Create,Update,Index,Show}`
70
70
  - `Operto::StaffTasks::{Create,Destroy}` · `Operto::StaffTaskTimes::Index`
71
71
  - `Operto::Issues::{Index,Close}` · `Operto::Homes::Index` · `Operto::Staff::Index`
72
72
 
data/lib/operto/error.rb CHANGED
@@ -3,4 +3,7 @@ module Operto
3
3
 
4
4
  # Raised by operations for malformed arguments before any network call.
5
5
  class ArgumentError < Error; end
6
+
7
+ # Raised when Operto answers a single-record read with nothing.
8
+ class NotFoundError < Error; end
6
9
  end
@@ -46,7 +46,7 @@ module Operto
46
46
  return if value.blank?
47
47
 
48
48
  Date.strptime(value.to_s, '%Y%m%d')
49
- rescue ArgumentError
49
+ rescue Date::Error
50
50
  value
51
51
  end
52
52
  end
@@ -19,13 +19,5 @@ module Operto
19
19
  key, reason = attrs.first
20
20
  raise Operto::ArgumentError, "#{key}: #{reason}"
21
21
  end
22
-
23
- def required_attribute!(key)
24
- raise Operto::ArgumentError, "#{key}: required"
25
- end
26
-
27
- def invalid_attribute!(key, type:)
28
- raise Operto::ArgumentError, "#{key}: expected #{type}"
29
- end
30
22
  end
31
23
  end
@@ -51,7 +51,7 @@ module Operto
51
51
  return if value.blank?
52
52
 
53
53
  Date.parse(value.to_s)
54
- rescue ArgumentError
54
+ rescue Date::Error
55
55
  value
56
56
  end
57
57
  end
@@ -8,8 +8,8 @@ module Operto
8
8
  def call(attributes:)
9
9
  validate_arguments!(attributes)
10
10
 
11
- request_attributes = prepare_attributes(attributes)
12
- response = Operto::Client.connection.post('tasks', request_attributes)
11
+ # A create always sends a description, '-' when there is none.
12
+ response = Operto::Client.connection.post('tasks', request_attributes(attributes.reverse_merge(description: nil)))
13
13
 
14
14
  handle_task_response(response)
15
15
  rescue StandardError => e
@@ -22,7 +22,7 @@ module Operto
22
22
  argument! attributes: :invalid unless attributes.is_a?(Hash) && attributes.present?
23
23
 
24
24
  required_keys.each do |key|
25
- required_attribute!(key) if attributes[key].blank?
25
+ argument!(key => :required) if attributes[key].blank?
26
26
  end
27
27
  end
28
28
 
@@ -3,6 +3,7 @@ module Operto
3
3
  class Index
4
4
  include Operto::Operation
5
5
  include FilteredPagination
6
+ include Shared
6
7
 
7
8
  FILTER_MAPPINGS = {
8
9
  property_id: 'PropertyID',
@@ -37,34 +38,13 @@ module Operto
37
38
  private
38
39
 
39
40
  def format_results(body)
40
- results = body[:data].map do |task|
41
- {
42
- task_id: task[:TaskID],
43
- property_id: task[:PropertyID],
44
- approved_at: task[:ApprovedDate]&.to_datetime,
45
- completed_at: task[:CompleteConfirmedDate]&.to_datetime,
46
- staff: extract_staff(task[:Staff])
47
- }
48
- end
41
+ results = body[:data].map { |task| normalize_task(task) }
49
42
 
50
43
  {
51
44
  results:,
52
45
  count: body[:total_items] || results.size
53
46
  }
54
47
  end
55
-
56
- def extract_staff(staff_array)
57
- return [] if staff_array.blank?
58
-
59
- staff_array.map do |staff|
60
- {
61
- staff_id: staff[:StaffID],
62
- name: staff[:Name],
63
- email: staff[:Email]&.downcase,
64
- active: staff[:Active]
65
- }
66
- end
67
- end
68
48
  end
69
49
  end
70
50
  end
@@ -1,31 +1,30 @@
1
1
  module Operto
2
2
  module Tasks
3
3
  module Shared
4
- private
4
+ COMPLETED_VALUES = [true, '1', 1].freeze
5
5
 
6
- def prepare_attributes(attrs)
7
- base_attrs = {
8
- PropertyID: attrs[:house_id],
9
- TaskRuleID: attrs[:rule_id],
10
- TaskName: attrs[:name],
11
- TaskDescription: attrs[:description].presence || '-'
12
- }.compact_blank
6
+ private
13
7
 
14
- base_attrs.merge(datetime_attributes(attrs))
8
+ # Only the given keys travel, so an update never resets what the caller left out.
9
+ def request_attributes(attrs)
10
+ request = { PropertyID: attrs[:house_id], TaskRuleID: attrs[:rule_id], TaskName: attrs[:name] }.compact_blank
11
+ request[:TaskDescription] = attrs[:description].presence || '-' if attrs.key?(:description)
12
+ request.merge!(start_attributes(attrs[:start_date])) if attrs[:start_date]
13
+ request.merge!(complete_by_attributes(attrs[:end_date])) if attrs[:end_date]
14
+ request
15
15
  end
16
16
 
17
- def datetime_attributes(attrs)
18
- h = {}
17
+ def start_attributes(value)
18
+ start_date = parse_datetime(value)
19
+ date = as_date(start_date)
19
20
 
20
- start_date = parse_datetime(attrs[:start_date])
21
- h[:TaskDate] = h[:TaskStartDate] = as_date(start_date)
22
- h[:TaskTime] = h[:TaskStartTime] = start_date.hour
21
+ { TaskStartDate: date, TaskDate: date, TaskStartTime: start_date.hour, TaskTime: start_date.hour }
22
+ end
23
23
 
24
- complete_by = parse_datetime(attrs[:end_date])
25
- h[:TaskCompleteByDate] = as_date(complete_by)
26
- h[:TaskCompleteByTime] = complete_by.hour
24
+ def complete_by_attributes(value)
25
+ complete_by = parse_datetime(value)
27
26
 
28
- h
27
+ { TaskCompleteByDate: as_date(complete_by), TaskCompleteByTime: complete_by.hour }
29
28
  end
30
29
 
31
30
  def parse_datetime(value)
@@ -39,6 +38,50 @@ module Operto
39
38
  def handle_task_response(response)
40
39
  Operto::Client.handle_response(response) { |body| { task_id: body[:TaskID] } }
41
40
  end
41
+
42
+ # The list and the single-task read send "10:00" and "0"/"1"; a write echoes 10 and false.
43
+ def normalize_task(task)
44
+ {
45
+ task_id: task[:TaskID],
46
+ property_id: task[:PropertyID],
47
+ property_booking_id: task[:PropertyBookingID],
48
+ rule_id: task[:TaskRuleID],
49
+ name: task[:TaskName]&.strip,
50
+ description: task[:TaskDescription],
51
+ **task_schedule(task),
52
+ **task_progress(task)
53
+ }
54
+ end
55
+
56
+ def task_schedule(task)
57
+ {
58
+ starts_at: task_time(task[:TaskStartDate] || task[:TaskDate], task[:TaskStartTime] || task[:TaskTime]),
59
+ due_at: task_time(task[:TaskCompleteByDate], task[:TaskCompleteByTime])
60
+ }
61
+ end
62
+
63
+ def task_progress(task)
64
+ {
65
+ active: task[:TaskActive] != false,
66
+ completed: COMPLETED_VALUES.include?(task[:Completed]),
67
+ approved_at: task[:ApprovedDate]&.to_datetime,
68
+ completed_at: task[:CompleteConfirmedDate]&.to_datetime,
69
+ staff: extract_staff(task[:Staff])
70
+ }
71
+ end
72
+
73
+ def task_time(date, time)
74
+ return if date.blank?
75
+
76
+ hour, minute = time.to_s.split(':')
77
+ Time.zone.parse(date.to_s).change(hour: hour.to_i, min: minute.to_i)
78
+ end
79
+
80
+ def extract_staff(staff)
81
+ Array(staff).map do |member|
82
+ { staff_id: member[:StaffID], name: member[:Name], email: member[:Email]&.downcase, active: member[:Active] }
83
+ end
84
+ end
42
85
  end
43
86
  end
44
87
  end
@@ -0,0 +1,25 @@
1
+ module Operto
2
+ module Tasks
3
+ class Show
4
+ include Operto::Operation
5
+ include Shared
6
+
7
+ # @rbs (task_id: ::String | ::Integer) -> Dry::Monads::Result[::Hash[::Symbol, untyped]]
8
+ def call(task_id:)
9
+ argument! task_id: :required if task_id.blank?
10
+
11
+ response = Operto::Client.connection.get("tasks/#{task_id}")
12
+
13
+ Operto::Client.handle_response(response) do |body|
14
+ # A missing task comes back as an empty list, not a 404.
15
+ task = body[:data]&.first
16
+ raise Operto::NotFoundError, "Task #{task_id} not found" unless task
17
+
18
+ normalize_task(task)
19
+ end
20
+ rescue StandardError => e
21
+ Failure(e)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -4,12 +4,11 @@ module Operto
4
4
  include Operto::Operation
5
5
  include Shared
6
6
 
7
- # @rbs (task_id: ::String, attributes: ::Hash[::Symbol, untyped]) -> Dry::Monads::Result[::Hash[::Symbol, untyped]]
7
+ # @rbs (task_id: ::String | ::Integer, attributes: ::Hash[::Symbol, untyped]) -> Dry::Monads::Result[::Hash[::Symbol, untyped]]
8
8
  def call(task_id:, attributes:)
9
9
  validate_arguments!(task_id, attributes)
10
10
 
11
- request_attributes = prepare_attributes(attributes).compact_blank
12
- response = Operto::Client.connection.put("tasks/#{task_id}", request_attributes)
11
+ response = Operto::Client.connection.put("tasks/#{task_id}", request_attributes(attributes))
13
12
 
14
13
  handle_task_response(response)
15
14
  rescue StandardError => e
@@ -1,3 +1,3 @@
1
1
  module Operto
2
- VERSION = '0.2.0'.freeze
2
+ VERSION = '0.3.0'.freeze
3
3
  end
data/lib/operto.rb CHANGED
@@ -18,6 +18,7 @@ require_relative 'operto/tasks/shared'
18
18
  require_relative 'operto/tasks/create'
19
19
  require_relative 'operto/tasks/update'
20
20
  require_relative 'operto/tasks/index'
21
+ require_relative 'operto/tasks/show'
21
22
  require_relative 'operto/staff_tasks/create'
22
23
  require_relative 'operto/staff_tasks/destroy'
23
24
  require_relative 'operto/staff_task_times/index'
data/sig/operto.rbs CHANGED
@@ -13,6 +13,10 @@ module Operto
13
13
  class ArgumentError < Error
14
14
  end
15
15
 
16
+ # Raised when Operto answers a single-record read with nothing.
17
+ class NotFoundError < Error
18
+ end
19
+
16
20
  class Config
17
21
  attr_accessor client_id: ::String?
18
22
  attr_accessor secret: ::String?
@@ -55,7 +59,12 @@ module Operto
55
59
  end
56
60
 
57
61
  class Update
58
- def call: (task_id: ::String, attributes: ::Hash[::Symbol, untyped]) -> ::Dry::Monads::Result[::Hash[::Symbol, untyped]]
62
+ def call: (task_id: ::String | ::Integer, attributes: ::Hash[::Symbol, untyped]) -> ::Dry::Monads::Result[::Hash[::Symbol, untyped]]
63
+ alias call! call
64
+ end
65
+
66
+ class Show
67
+ def call: (task_id: ::String | ::Integer) -> ::Dry::Monads::Result[::Hash[::Symbol, untyped]]
59
68
  alias call! call
60
69
  end
61
70
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: operto
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Guestit
@@ -107,6 +107,7 @@ files:
107
107
  - lib/operto/tasks/create.rb
108
108
  - lib/operto/tasks/index.rb
109
109
  - lib/operto/tasks/shared.rb
110
+ - lib/operto/tasks/show.rb
110
111
  - lib/operto/tasks/update.rb
111
112
  - lib/operto/version.rb
112
113
  - sig/operto.rbs
@@ -132,7 +133,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
132
133
  - !ruby/object:Gem::Version
133
134
  version: '0'
134
135
  requirements: []
135
- rubygems_version: 4.0.17
136
+ rubygems_version: 4.0.20
136
137
  specification_version: 4
137
138
  summary: Client for the Operto Teams API
138
139
  test_files: []