crisphive 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.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +98 -10
  3. data/lib/crisphive/api/business_skill_api.rb +348 -0
  4. data/lib/crisphive/api/customer_api.rb +7 -4
  5. data/lib/crisphive/api/job_request_business_api.rb +243 -27
  6. data/lib/crisphive/api/job_types_api.rb +208 -0
  7. data/lib/crisphive/api/permission_api.rb +79 -0
  8. data/lib/crisphive/api/service_area_api.rb +208 -0
  9. data/lib/crisphive/api/technician_api.rb +8 -8
  10. data/lib/crisphive/api/technician_time_off_api.rb +240 -0
  11. data/lib/crisphive/api/vehicle_api.rb +208 -0
  12. data/lib/crisphive/api/webhooks_api.rb +213 -0
  13. data/lib/crisphive/models/api_internal_techtimeoff_actions.rb +232 -0
  14. data/lib/crisphive/models/api_internal_techtimeoff_status.rb +42 -0
  15. data/lib/crisphive/models/assignable_group.rb +289 -0
  16. data/lib/crisphive/models/book_and_confirm_job_request200_response.rb +253 -0
  17. data/lib/crisphive/models/commit_absence_resolve200_response.rb +253 -0
  18. data/lib/crisphive/models/create_job_type200_response.rb +253 -0
  19. data/lib/crisphive/models/create_service_area200_response.rb +253 -0
  20. data/lib/crisphive/models/create_skill200_response.rb +253 -0
  21. data/lib/crisphive/models/create_skill_category200_response.rb +253 -0
  22. data/lib/crisphive/models/create_technician_time_off200_response.rb +253 -0
  23. data/lib/crisphive/models/create_vehicle200_response.rb +253 -0
  24. data/lib/crisphive/models/create_webhook_endpoint200_response.rb +253 -0
  25. data/lib/crisphive/models/customer.rb +11 -1
  26. data/lib/crisphive/models/customer_contact.rb +25 -5
  27. data/lib/crisphive/models/customer_create_request.rb +12 -2
  28. data/lib/crisphive/models/customer_list_item.rb +11 -1
  29. data/lib/crisphive/models/customer_quick_books_link.rb +235 -0
  30. data/lib/crisphive/models/customer_service_area_ref.rb +225 -0
  31. data/lib/crisphive/models/customer_technician_ref.rb +225 -0
  32. data/lib/crisphive/models/customer_update_request.rb +23 -20
  33. data/lib/crisphive/models/internal_jobrequest_http_address_req.rb +475 -0
  34. data/lib/crisphive/models/job_request.rb +11 -1
  35. data/lib/crisphive/models/job_request_absence_alternative.rb +356 -0
  36. data/lib/crisphive/models/job_request_absence_alternative_cost.rb +275 -0
  37. data/lib/crisphive/models/job_request_absence_assignment.rb +319 -0
  38. data/lib/crisphive/models/job_request_absence_commit_request.rb +296 -0
  39. data/lib/crisphive/models/job_request_absence_notification.rb +283 -0
  40. data/lib/crisphive/models/job_request_absence_plan.rb +322 -0
  41. data/lib/crisphive/models/job_request_absence_preview_request.rb +280 -0
  42. data/lib/crisphive/models/job_request_absence_resolved_job.rb +499 -0
  43. data/lib/crisphive/models/job_request_absence_solver.rb +289 -0
  44. data/lib/crisphive/models/job_request_absence_time_off.rb +281 -0
  45. data/lib/crisphive/models/job_request_absence_unresolved_job.rb +364 -0
  46. data/lib/crisphive/models/job_request_action_summary.rb +1 -1
  47. data/lib/crisphive/models/job_request_attention_summary.rb +2 -2
  48. data/lib/crisphive/models/job_request_blocker.rb +363 -0
  49. data/lib/crisphive/models/job_request_blocker_ref.rb +223 -0
  50. data/lib/crisphive/models/job_request_book_and_confirm_customer.rb +302 -0
  51. data/lib/crisphive/models/job_request_book_and_confirm_emergency.rb +315 -0
  52. data/lib/crisphive/models/job_request_book_and_confirm_emergency_result.rb +279 -0
  53. data/lib/crisphive/models/job_request_book_and_confirm_refusal.rb +280 -0
  54. data/lib/crisphive/models/job_request_book_and_confirm_request.rb +466 -0
  55. data/lib/crisphive/models/job_request_book_and_confirm_response.rb +295 -0
  56. data/lib/crisphive/models/job_request_confirm_request.rb +14 -4
  57. data/lib/crisphive/models/job_request_create_request.rb +1 -1
  58. data/lib/crisphive/models/job_request_emergency_candidate.rb +26 -4
  59. data/lib/crisphive/models/job_request_emergency_candidates.rb +1 -1
  60. data/lib/crisphive/models/job_request_emergency_candidates_request.rb +24 -11
  61. data/lib/crisphive/models/job_request_emergency_commit_request.rb +11 -1
  62. data/lib/crisphive/models/job_request_emergency_inline_job.rb +353 -0
  63. data/lib/crisphive/models/job_request_emergency_plan.rb +25 -3
  64. data/lib/crisphive/models/job_request_emergency_preview_request.rb +24 -11
  65. data/lib/crisphive/models/job_request_move_commit_req.rb +11 -1
  66. data/lib/crisphive/models/job_request_move_plan.rb +11 -1
  67. data/lib/crisphive/models/job_request_move_preview_req.rb +11 -1
  68. data/lib/crisphive/models/job_request_move_warning.rb +32 -8
  69. data/lib/crisphive/models/job_request_priority_update_result.rb +217 -0
  70. data/lib/crisphive/models/job_request_quote_request.rb +14 -11
  71. data/lib/crisphive/models/job_request_reassign_fallback.rb +287 -0
  72. data/lib/crisphive/models/job_request_skill_summary.rb +1 -1
  73. data/lib/crisphive/models/job_request_status_summary.rb +1 -1
  74. data/lib/crisphive/models/job_request_time_segments.rb +2 -2
  75. data/lib/crisphive/models/job_type.rb +42 -2
  76. data/lib/crisphive/models/job_type_create_request.rb +399 -0
  77. data/lib/crisphive/models/job_type_create_response.rb +215 -0
  78. data/lib/crisphive/models/job_type_update_request.rb +400 -0
  79. data/lib/crisphive/models/list_assignable_groups200_response.rb +255 -0
  80. data/lib/crisphive/models/list_technician_time_off200_response.rb +253 -0
  81. data/lib/crisphive/models/list_webhook_event_types200_response.rb +255 -0
  82. data/lib/crisphive/models/quote_job_request200_response.rb +253 -0
  83. data/lib/crisphive/models/service_area.rb +21 -1
  84. data/lib/crisphive/models/service_area_create_request.rb +455 -0
  85. data/lib/crisphive/models/service_area_create_response.rb +215 -0
  86. data/lib/crisphive/models/service_area_update_request.rb +451 -0
  87. data/lib/crisphive/models/skill_category_create_request.rb +270 -0
  88. data/lib/crisphive/models/skill_category_create_response.rb +215 -0
  89. data/lib/crisphive/models/skill_create_request.rb +270 -0
  90. data/lib/crisphive/models/skill_create_response.rb +215 -0
  91. data/lib/crisphive/models/skill_update_request.rb +281 -0
  92. data/lib/crisphive/models/technician.rb +64 -2
  93. data/lib/crisphive/models/technician_create_request.rb +14 -11
  94. data/lib/crisphive/models/technician_time_off.rb +334 -0
  95. data/lib/crisphive/models/technician_time_off_create_request.rb +285 -0
  96. data/lib/crisphive/models/technician_time_off_create_response.rb +215 -0
  97. data/lib/crisphive/models/technician_time_off_list.rb +246 -0
  98. data/lib/crisphive/models/technician_update_request.rb +2 -2
  99. data/lib/crisphive/models/vehicle_create_request.rb +420 -0
  100. data/lib/crisphive/models/vehicle_create_response.rb +215 -0
  101. data/lib/crisphive/models/vehicle_update_request.rb +440 -0
  102. data/lib/crisphive/models/webhook_create_request.rb +320 -0
  103. data/lib/crisphive/models/webhook_create_response.rb +391 -0
  104. data/lib/crisphive/models/webhook_verification.rb +235 -0
  105. data/lib/crisphive/version.rb +1 -1
  106. data/lib/crisphive.rb +66 -0
  107. metadata +68 -2
@@ -19,8 +19,150 @@ module Crisphive
19
19
  def initialize(api_client = ApiClient.default)
20
20
  @api_client = api_client
21
21
  end
22
+ # Book, schedule and confirm a job in one call
23
+ # Creates the job, quotes it (job_duration_minutes, or the job type's default_duration_minutes) and confirms it at scheduled_at on the customer's behalf — the dashboard's \"book for a caller\" in a single request, built for voice agents and automation platforms that cannot run create → quote → slots → confirm. Customer: send `customer_id`, or `customer` (+ `address`) to match-or-create by phone/email — a caller who already exists is matched, never duplicated. Find a caller first with listCustomers?phone=. Every input is validated BEFORE anything is written (time and business timezone, future start, a duration or a job-type default, non-negative mobilization/demobilization minutes, active job type, phone format); those failures are ordinary 4xx and create nothing. ⚠️ A `technician_id` the engine cannot honour at that time is refused BEFORE anything is written: `409 JOB_REQUEST_TECH_INFEASIBLE`, no job, no customer, no webhook. Change the technician or the time and send again with a NEW Idempotency-Key: like every 4xx here the 409 is cached against its key for 24h, so the same key replays it (same body) or answers 422 IDEMPOTENCY_KEY_REUSE (changed body). This check is BEST-EFFORT — if the scheduling engine cannot be reached it is skipped and the call proceeds, so a pinned request can still come back as the 200 + `refusal` below. Handle both. ⚠️ ONE carve-out: a `p0` whose pinned technician is merely BUSY is NOT refused here. The job is created and the call answers 200 + `refusal` carrying JOB_REQUEST_P0_REQUIRES_DISPLACEMENT, because the remedy for that code is the emergency flow and listEmergencyCandidates / previewEmergencyReschedule / commitEmergencyReschedule all require `emergency_job_id` — a 409 carries no job id. Read `job_id` off the 200 and run the emergency flow; do not re-book. Every other doomed pin on a `p0` (out of area, missing skills, not a lead) still answers 409. ⚠️ A SECOND carve-out, narrower: for a `p0`, a PERSONAL-CALENDAR overlap on the pinned technician is not refused here either — naming them is treated as having already phoned them — and the call answers 200 + `confirmed: true`. Nothing on this response discloses it; the only record is the business's activity feed (`job_calendar_override`). `calendar_busy`/`calendar_conflict` therefore never appear in the 409 reason vocabulary below when the pin is a `p0` naming a technician explicitly (this endpoint never books a crew, so the exclusion is unconditional here). ⚠️ Otherwise, once the job is created it is never discarded, and the call answers 200 even if scheduling then fails: `confirmed: false` with `refusal` (stage + the exact error_code/data the quote or confirm endpoint would have returned, e.g. JOB_REQUEST_NO_TECHNICIAN_AVAILABLE with blockers). The job is then quoted and waiting in the coordinator's queue — tell the caller the office will confirm a time. Retry with the SAME Idempotency-Key to replay the result; a new key books a second job. P0 dispatch: send `emergency` with `priority: \"p0\"`. `dispatch: \"now\"` plans the displacement cascade BEFORE anything is written — a refusal is a 409 creating nothing — then books, quotes and commits it. `dispatch: \"later\"` parks a quoted P0 in the tray and ignores the other fields. ⚠️ `emergency.expected_move_ids` is REQUIRED with `dispatch: \"now\"` and has three meanings: omitted is refused, `[]` means \"my preview showed no displacement\" and commits a free lane, and a named set is the fence. Get the ids from previewEmergencyReschedule. ⚠️ A refusal AFTER the job is created answers 200 with `confirmed: false` and `refusal.stage: \"dispatch\"`. Read `job_id`, re-preview with previewEmergencyReschedule and commit with commitEmergencyReschedule. Never re-book.
24
+ # @param job_request_book_and_confirm_request [JobRequestBookAndConfirmRequest] Book and confirm in one call
25
+ # @param [Hash] opts the optional parameters
26
+ # @option opts [String] :idempotency_key Makes retries safe: a repeat send with the same key returns the original result instead of booking again
27
+ # @return [BookAndConfirmJobRequest200Response]
28
+ def book_and_confirm_job_request(job_request_book_and_confirm_request, opts = {})
29
+ data, _status_code, _headers = book_and_confirm_job_request_with_http_info(job_request_book_and_confirm_request, opts)
30
+ data
31
+ end
32
+
33
+ # Book, schedule and confirm a job in one call
34
+ # Creates the job, quotes it (job_duration_minutes, or the job type's default_duration_minutes) and confirms it at scheduled_at on the customer's behalf — the dashboard's \"book for a caller\" in a single request, built for voice agents and automation platforms that cannot run create → quote → slots → confirm. Customer: send `customer_id`, or `customer` (+ `address`) to match-or-create by phone/email — a caller who already exists is matched, never duplicated. Find a caller first with listCustomers?phone=. Every input is validated BEFORE anything is written (time and business timezone, future start, a duration or a job-type default, non-negative mobilization/demobilization minutes, active job type, phone format); those failures are ordinary 4xx and create nothing. ⚠️ A `technician_id` the engine cannot honour at that time is refused BEFORE anything is written: `409 JOB_REQUEST_TECH_INFEASIBLE`, no job, no customer, no webhook. Change the technician or the time and send again with a NEW Idempotency-Key: like every 4xx here the 409 is cached against its key for 24h, so the same key replays it (same body) or answers 422 IDEMPOTENCY_KEY_REUSE (changed body). This check is BEST-EFFORT — if the scheduling engine cannot be reached it is skipped and the call proceeds, so a pinned request can still come back as the 200 + `refusal` below. Handle both. ⚠️ ONE carve-out: a `p0` whose pinned technician is merely BUSY is NOT refused here. The job is created and the call answers 200 + `refusal` carrying JOB_REQUEST_P0_REQUIRES_DISPLACEMENT, because the remedy for that code is the emergency flow and listEmergencyCandidates / previewEmergencyReschedule / commitEmergencyReschedule all require `emergency_job_id` — a 409 carries no job id. Read `job_id` off the 200 and run the emergency flow; do not re-book. Every other doomed pin on a `p0` (out of area, missing skills, not a lead) still answers 409. ⚠️ A SECOND carve-out, narrower: for a `p0`, a PERSONAL-CALENDAR overlap on the pinned technician is not refused here either — naming them is treated as having already phoned them — and the call answers 200 + `confirmed: true`. Nothing on this response discloses it; the only record is the business's activity feed (`job_calendar_override`). `calendar_busy`/`calendar_conflict` therefore never appear in the 409 reason vocabulary below when the pin is a `p0` naming a technician explicitly (this endpoint never books a crew, so the exclusion is unconditional here). ⚠️ Otherwise, once the job is created it is never discarded, and the call answers 200 even if scheduling then fails: `confirmed: false` with `refusal` (stage + the exact error_code/data the quote or confirm endpoint would have returned, e.g. JOB_REQUEST_NO_TECHNICIAN_AVAILABLE with blockers). The job is then quoted and waiting in the coordinator's queue — tell the caller the office will confirm a time. Retry with the SAME Idempotency-Key to replay the result; a new key books a second job. P0 dispatch: send `emergency` with `priority: \"p0\"`. `dispatch: \"now\"` plans the displacement cascade BEFORE anything is written — a refusal is a 409 creating nothing — then books, quotes and commits it. `dispatch: \"later\"` parks a quoted P0 in the tray and ignores the other fields. ⚠️ `emergency.expected_move_ids` is REQUIRED with `dispatch: \"now\"` and has three meanings: omitted is refused, `[]` means \"my preview showed no displacement\" and commits a free lane, and a named set is the fence. Get the ids from previewEmergencyReschedule. ⚠️ A refusal AFTER the job is created answers 200 with `confirmed: false` and `refusal.stage: \"dispatch\"`. Read `job_id`, re-preview with previewEmergencyReschedule and commit with commitEmergencyReschedule. Never re-book.
35
+ # @param job_request_book_and_confirm_request [JobRequestBookAndConfirmRequest] Book and confirm in one call
36
+ # @param [Hash] opts the optional parameters
37
+ # @option opts [String] :idempotency_key Makes retries safe: a repeat send with the same key returns the original result instead of booking again
38
+ # @return [Array<(BookAndConfirmJobRequest200Response, Integer, Hash)>] BookAndConfirmJobRequest200Response data, response status code and response headers
39
+ def book_and_confirm_job_request_with_http_info(job_request_book_and_confirm_request, opts = {})
40
+ if @api_client.config.debugging
41
+ @api_client.config.logger.debug 'Calling API: JobRequestBusinessApi.book_and_confirm_job_request ...'
42
+ end
43
+ # verify the required parameter 'job_request_book_and_confirm_request' is set
44
+ if @api_client.config.client_side_validation && job_request_book_and_confirm_request.nil?
45
+ fail ArgumentError, "Missing the required parameter 'job_request_book_and_confirm_request' when calling JobRequestBusinessApi.book_and_confirm_job_request"
46
+ end
47
+ # resource path
48
+ local_var_path = '/job-requests/book-and-confirm'
49
+
50
+ # query parameters
51
+ query_params = opts[:query_params] || {}
52
+
53
+ # header parameters
54
+ header_params = opts[:header_params] || {}
55
+ # HTTP header 'Accept' (if needed)
56
+ header_params['Accept'] = @api_client.select_header_accept(['application/json']) unless header_params['Accept']
57
+ # HTTP header 'Content-Type'
58
+ content_type = @api_client.select_header_content_type(['application/json'])
59
+ if !content_type.nil?
60
+ header_params['Content-Type'] = content_type
61
+ end
62
+ header_params[:'Idempotency-Key'] = opts[:'idempotency_key'] if !opts[:'idempotency_key'].nil?
63
+
64
+ # form parameters
65
+ form_params = opts[:form_params] || {}
66
+
67
+ # http body (model)
68
+ post_body = opts[:debug_body] || @api_client.object_to_http_body(job_request_book_and_confirm_request)
69
+
70
+ # return_type
71
+ return_type = opts[:debug_return_type] || 'BookAndConfirmJobRequest200Response'
72
+
73
+ # auth_names
74
+ auth_names = opts[:debug_auth_names] || ['ApiKeyAuth']
75
+
76
+ new_options = opts.merge(
77
+ :operation => :"JobRequestBusinessApi.book_and_confirm_job_request",
78
+ :header_params => header_params,
79
+ :query_params => query_params,
80
+ :form_params => form_params,
81
+ :body => post_body,
82
+ :auth_names => auth_names,
83
+ :return_type => return_type
84
+ )
85
+
86
+ data, status_code, headers = @api_client.call_api(:POST, local_var_path, new_options)
87
+ if @api_client.config.debugging
88
+ @api_client.config.logger.debug "API called: JobRequestBusinessApi#book_and_confirm_job_request\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"
89
+ end
90
+ return data, status_code, headers
91
+ end
92
+
93
+ # Commit the previewed re-staffing of a technician's day
94
+ # Applies the plan returned by /absence/preview, ATOMICALLY: every assignment in one transaction (all or nothing), each job re-staffed onto its alternate at its unchanged window, its \"needs attention\" flag cleared, its status_version bumped. Commit VERIFIES and never re-solves — send `assignments[]` copied from `preview.resolved[]` (you may drop rows, never add or re-point them) and, to ACCEPT a priced alternative from `preview.unresolved[].alternatives[]`, the same row plus its `alternative_kind` and (for a reschedule kind) its `start_at`/`end_at`; the engine re-checks that technician at that window under exactly that relaxation. A reschedule kind rewrites `scheduled_at`, notifies the customer of the NEW TIME (`job_rescheduled`, never `tech_reassigned` on top) and fires `job_request.rescheduled`; the response row then carries `alternative_kind`, `cost`, `original_start_at`/`original_end_at` and `window_preserved: false`. Requires a time-off record covering EVERY day of the range for the technician (pending or approved); a pending one is APPROVED by the commit, because the engine's feasibility filter reads approved time-off only and without it the absent technician stays bookable everywhere else. The response mirrors the preview plus per-job `notification` evidence (dispatched | skipped + reason — what the routing WILL do, never proof of delivery), `attention_cleared`, and `time_off`. Supports Idempotency-Key. Requires job_manage AND schedule_manage (the approval is a scheduling action). See ABSENCE_RESOLVE_DESIGN.md. 409 NEXT STEPS: ABSENCE_RESOLVE_TIME_OFF_REQUIRED — the absence is not recorded well enough to commit against: `data.uncovered_dates[]` (days no eligible record touches) and `data.uncovered_jobs[]` (jobs no single eligible record spans); eligible = approved records, plus PENDING records whose span lies INSIDE the range — a pending record WIDER than the range (somebody's leave request) is never approved by this commit and is listed in `data.pending_wider_time_off_ids[]` for a human to decide. Record a sick-day time-off via `data.create_via` (POST /business/technician-time-off, pending is enough), then commit again. ABSENCE_RESOLVE_PLAN_DRIFTED — the world moved since the preview; `data.drifted[]` names the job (job_id/short_code/expected_version) and `reason` says how: `version` (the row changed or left the technician's lane), `infeasible` (the alternate can no longer take it), `occupied` (the alternate's lane overlapped after the write). No job was written; `data.time_offs[]` lists any pending record the commit had already approved — re-preview, show the new plan, commit again. ABSENCE_RESOLVE_NO_ORPHANED_JOBS — the board is empty for the range.
95
+ # @param job_request_absence_commit_request [JobRequestAbsenceCommitRequest] preview scope + the previewed assignments to apply
96
+ # @param [Hash] opts the optional parameters
97
+ # @option opts [String] :idempotency_key Unique key making retries safe: a repeat send with the same key replays the original response (header Idempotent-Replayed: true) instead of re-running the operation. Reusing a key with a different body returns 422 IDEMPOTENCY_KEY_REUSE.
98
+ # @return [CommitAbsenceResolve200Response]
99
+ def commit_absence_resolve(job_request_absence_commit_request, opts = {})
100
+ data, _status_code, _headers = commit_absence_resolve_with_http_info(job_request_absence_commit_request, opts)
101
+ data
102
+ end
103
+
104
+ # Commit the previewed re-staffing of a technician&#39;s day
105
+ # Applies the plan returned by /absence/preview, ATOMICALLY: every assignment in one transaction (all or nothing), each job re-staffed onto its alternate at its unchanged window, its \&quot;needs attention\&quot; flag cleared, its status_version bumped. Commit VERIFIES and never re-solves — send &#x60;assignments[]&#x60; copied from &#x60;preview.resolved[]&#x60; (you may drop rows, never add or re-point them) and, to ACCEPT a priced alternative from &#x60;preview.unresolved[].alternatives[]&#x60;, the same row plus its &#x60;alternative_kind&#x60; and (for a reschedule kind) its &#x60;start_at&#x60;/&#x60;end_at&#x60;; the engine re-checks that technician at that window under exactly that relaxation. A reschedule kind rewrites &#x60;scheduled_at&#x60;, notifies the customer of the NEW TIME (&#x60;job_rescheduled&#x60;, never &#x60;tech_reassigned&#x60; on top) and fires &#x60;job_request.rescheduled&#x60;; the response row then carries &#x60;alternative_kind&#x60;, &#x60;cost&#x60;, &#x60;original_start_at&#x60;/&#x60;original_end_at&#x60; and &#x60;window_preserved: false&#x60;. Requires a time-off record covering EVERY day of the range for the technician (pending or approved); a pending one is APPROVED by the commit, because the engine&#39;s feasibility filter reads approved time-off only and without it the absent technician stays bookable everywhere else. The response mirrors the preview plus per-job &#x60;notification&#x60; evidence (dispatched | skipped + reason — what the routing WILL do, never proof of delivery), &#x60;attention_cleared&#x60;, and &#x60;time_off&#x60;. Supports Idempotency-Key. Requires job_manage AND schedule_manage (the approval is a scheduling action). See ABSENCE_RESOLVE_DESIGN.md. 409 NEXT STEPS: ABSENCE_RESOLVE_TIME_OFF_REQUIRED — the absence is not recorded well enough to commit against: &#x60;data.uncovered_dates[]&#x60; (days no eligible record touches) and &#x60;data.uncovered_jobs[]&#x60; (jobs no single eligible record spans); eligible &#x3D; approved records, plus PENDING records whose span lies INSIDE the range — a pending record WIDER than the range (somebody&#39;s leave request) is never approved by this commit and is listed in &#x60;data.pending_wider_time_off_ids[]&#x60; for a human to decide. Record a sick-day time-off via &#x60;data.create_via&#x60; (POST /business/technician-time-off, pending is enough), then commit again. ABSENCE_RESOLVE_PLAN_DRIFTED — the world moved since the preview; &#x60;data.drifted[]&#x60; names the job (job_id/short_code/expected_version) and &#x60;reason&#x60; says how: &#x60;version&#x60; (the row changed or left the technician&#39;s lane), &#x60;infeasible&#x60; (the alternate can no longer take it), &#x60;occupied&#x60; (the alternate&#39;s lane overlapped after the write). No job was written; &#x60;data.time_offs[]&#x60; lists any pending record the commit had already approved — re-preview, show the new plan, commit again. ABSENCE_RESOLVE_NO_ORPHANED_JOBS — the board is empty for the range.
106
+ # @param job_request_absence_commit_request [JobRequestAbsenceCommitRequest] preview scope + the previewed assignments to apply
107
+ # @param [Hash] opts the optional parameters
108
+ # @option opts [String] :idempotency_key Unique key making retries safe: a repeat send with the same key replays the original response (header Idempotent-Replayed: true) instead of re-running the operation. Reusing a key with a different body returns 422 IDEMPOTENCY_KEY_REUSE.
109
+ # @return [Array<(CommitAbsenceResolve200Response, Integer, Hash)>] CommitAbsenceResolve200Response data, response status code and response headers
110
+ def commit_absence_resolve_with_http_info(job_request_absence_commit_request, opts = {})
111
+ if @api_client.config.debugging
112
+ @api_client.config.logger.debug 'Calling API: JobRequestBusinessApi.commit_absence_resolve ...'
113
+ end
114
+ # verify the required parameter 'job_request_absence_commit_request' is set
115
+ if @api_client.config.client_side_validation && job_request_absence_commit_request.nil?
116
+ fail ArgumentError, "Missing the required parameter 'job_request_absence_commit_request' when calling JobRequestBusinessApi.commit_absence_resolve"
117
+ end
118
+ # resource path
119
+ local_var_path = '/job-requests/absence/commit'
120
+
121
+ # query parameters
122
+ query_params = opts[:query_params] || {}
123
+
124
+ # header parameters
125
+ header_params = opts[:header_params] || {}
126
+ # HTTP header 'Accept' (if needed)
127
+ header_params['Accept'] = @api_client.select_header_accept(['application/json']) unless header_params['Accept']
128
+ # HTTP header 'Content-Type'
129
+ content_type = @api_client.select_header_content_type(['application/json'])
130
+ if !content_type.nil?
131
+ header_params['Content-Type'] = content_type
132
+ end
133
+ header_params[:'Idempotency-Key'] = opts[:'idempotency_key'] if !opts[:'idempotency_key'].nil?
134
+
135
+ # form parameters
136
+ form_params = opts[:form_params] || {}
137
+
138
+ # http body (model)
139
+ post_body = opts[:debug_body] || @api_client.object_to_http_body(job_request_absence_commit_request)
140
+
141
+ # return_type
142
+ return_type = opts[:debug_return_type] || 'CommitAbsenceResolve200Response'
143
+
144
+ # auth_names
145
+ auth_names = opts[:debug_auth_names] || ['ApiKeyAuth']
146
+
147
+ new_options = opts.merge(
148
+ :operation => :"JobRequestBusinessApi.commit_absence_resolve",
149
+ :header_params => header_params,
150
+ :query_params => query_params,
151
+ :form_params => form_params,
152
+ :body => post_body,
153
+ :auth_names => auth_names,
154
+ :return_type => return_type
155
+ )
156
+
157
+ data, status_code, headers = @api_client.call_api(:POST, local_var_path, new_options)
158
+ if @api_client.config.debugging
159
+ @api_client.config.logger.debug "API called: JobRequestBusinessApi#commit_absence_resolve\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"
160
+ end
161
+ return data, status_code, headers
162
+ end
163
+
22
164
  # Commit emergency insert + cascade reschedule
23
- # Applies the cascade previewed by /emergency/preview: assigns the emergency job to the technician and pushes the displaced jobs back (or, with `displacement_mode=reassign`, re-staffs them onto their previewed alternates first), atomically. Supports Idempotency-Key. The server recomputes the plan under a lock and fences each job on its status_version — if anything changed since the preview it returns 409 EMERGENCY_RESCHEDULE_PLAN_DRIFTED (re-preview). Same body as preview + optional `emergency_expected_version`. Isolated feature (see EMERGENCY_RESCHEDULE_DESIGN.md). 409 NEXT STEPS: EMERGENCY_RESCHEDULE_PLAN_DRIFTED — the schedule changed between your preview and this commit (another booking/move won a lane): call /preview again, show the fresh plan, then commit. EMERGENCY_RESCHEDULE_SLOT_OCCUPIED — landing window blocked by an immovable anchor (P0/crew/multi-day): another tech or time. Other codes — same remedies as /candidates.
165
+ # Applies the cascade previewed by /emergency/preview: assigns the emergency job to the technician and pushes the displaced jobs back (or, with `displacement_mode=reassign`, re-staffs them onto their previewed alternates first), atomically. Supports Idempotency-Key. The server recomputes the plan under a lock and fences each job on its status_version — if anything changed since the preview it returns 409 EMERGENCY_RESCHEDULE_PLAN_DRIFTED (re-preview). Same body as preview + optional `emergency_expected_version`. `after_hours_override=true` must match the preview it followed — it drops the non-working-day rejection (this endpoint never runs the technician's working-hours/time-off feasibility check; that only happens on /candidates), and the response carries an AFTER_HOURS warning; the committed job's activity feed also records who authorized the after-hours placement. Isolated feature (see EMERGENCY_RESCHEDULE_DESIGN.md). 409 NEXT STEPS: EMERGENCY_RESCHEDULE_PLAN_DRIFTED — the schedule changed between your preview and this commit (another booking/move won a lane); `data.drifted[]` names the job(s) whose status_version moved when the fence can attribute it (absent, never empty, when only a length mismatch is known): call /preview again, show the fresh plan, then commit. EMERGENCY_RESCHEDULE_SLOT_OCCUPIED — landing window blocked by an immovable anchor (P0/crew/multi-day); `data.conflicts[]` names it: another tech or time. Other codes — same remedies and `data` shapes as /candidates. EMERGENCY_RESCHEDULE_NO_WORKING_DAY fires on a closed day without after_hours_override and carries `data.override_available: true` — confirm with the coordinator, then retry with the flag.
24
166
  # @param job_request_emergency_commit_request [JobRequestEmergencyCommitRequest] emergency insert spec
25
167
  # @param [Hash] opts the optional parameters
26
168
  # @option opts [String] :idempotency_key Unique key making retries safe: a repeat send with the same key replays the original response (header Idempotent-Replayed: true) instead of re-running the operation. Reusing a key with a different body returns 422 IDEMPOTENCY_KEY_REUSE.
@@ -31,7 +173,7 @@ module Crisphive
31
173
  end
32
174
 
33
175
  # Commit emergency insert + cascade reschedule
34
- # Applies the cascade previewed by /emergency/preview: assigns the emergency job to the technician and pushes the displaced jobs back (or, with &#x60;displacement_mode&#x3D;reassign&#x60;, re-staffs them onto their previewed alternates first), atomically. Supports Idempotency-Key. The server recomputes the plan under a lock and fences each job on its status_version — if anything changed since the preview it returns 409 EMERGENCY_RESCHEDULE_PLAN_DRIFTED (re-preview). Same body as preview + optional &#x60;emergency_expected_version&#x60;. Isolated feature (see EMERGENCY_RESCHEDULE_DESIGN.md). 409 NEXT STEPS: EMERGENCY_RESCHEDULE_PLAN_DRIFTED — the schedule changed between your preview and this commit (another booking/move won a lane): call /preview again, show the fresh plan, then commit. EMERGENCY_RESCHEDULE_SLOT_OCCUPIED — landing window blocked by an immovable anchor (P0/crew/multi-day): another tech or time. Other codes — same remedies as /candidates.
176
+ # Applies the cascade previewed by /emergency/preview: assigns the emergency job to the technician and pushes the displaced jobs back (or, with &#x60;displacement_mode&#x3D;reassign&#x60;, re-staffs them onto their previewed alternates first), atomically. Supports Idempotency-Key. The server recomputes the plan under a lock and fences each job on its status_version — if anything changed since the preview it returns 409 EMERGENCY_RESCHEDULE_PLAN_DRIFTED (re-preview). Same body as preview + optional &#x60;emergency_expected_version&#x60;. &#x60;after_hours_override&#x3D;true&#x60; must match the preview it followed — it drops the non-working-day rejection (this endpoint never runs the technician&#39;s working-hours/time-off feasibility check; that only happens on /candidates), and the response carries an AFTER_HOURS warning; the committed job&#39;s activity feed also records who authorized the after-hours placement. Isolated feature (see EMERGENCY_RESCHEDULE_DESIGN.md). 409 NEXT STEPS: EMERGENCY_RESCHEDULE_PLAN_DRIFTED — the schedule changed between your preview and this commit (another booking/move won a lane); &#x60;data.drifted[]&#x60; names the job(s) whose status_version moved when the fence can attribute it (absent, never empty, when only a length mismatch is known): call /preview again, show the fresh plan, then commit. EMERGENCY_RESCHEDULE_SLOT_OCCUPIED — landing window blocked by an immovable anchor (P0/crew/multi-day); &#x60;data.conflicts[]&#x60; names it: another tech or time. Other codes — same remedies and &#x60;data&#x60; shapes as /candidates. EMERGENCY_RESCHEDULE_NO_WORKING_DAY fires on a closed day without after_hours_override and carries &#x60;data.override_available: true&#x60; — confirm with the coordinator, then retry with the flag.
35
177
  # @param job_request_emergency_commit_request [JobRequestEmergencyCommitRequest] emergency insert spec
36
178
  # @param [Hash] opts the optional parameters
37
179
  # @option opts [String] :idempotency_key Unique key making retries safe: a repeat send with the same key replays the original response (header Idempotent-Replayed: true) instead of re-running the operation. Reusing a key with a different body returns 422 IDEMPOTENCY_KEY_REUSE.
@@ -91,7 +233,7 @@ module Crisphive
91
233
  end
92
234
 
93
235
  # Commit a schedule-board job move
94
- # Applies the move previewed by /move/preview: places the job on the technician at the new time and pushes the displaced jobs back, atomically (per-tech advisory lock; the server recomputes the plan and fences each job on its status_version — drift since the preview returns 409 SCHEDULE_MOVE_PLAN_DRIFTED, re-preview). Same body as preview + optional `expected_version`. See SCHEDULE_BOARD_DESIGN.md. 409 NEXT STEPS: SCHEDULE_MOVE_PLAN_DRIFTED — the schedule changed since your preview (or expected_move_ids no longer match): re-preview, show the fresh plan, commit again. All other codes — same remedies as /move/preview.
236
+ # Applies the move previewed by /move/preview: places the job on the technician at the new time and pushes the displaced jobs back, atomically (per-tech advisory lock; the server recomputes the plan and fences each job on its status_version — drift since the preview returns 409 SCHEDULE_MOVE_PLAN_DRIFTED, re-preview). Same body as preview + optional `expected_version`. See SCHEDULE_BOARD_DESIGN.md. 409 NEXT STEPS: SCHEDULE_MOVE_PLAN_DRIFTED — the schedule changed since your preview (or expected_move_ids/expected_member_ids no longer match): re-preview, show the fresh plan, commit again; carries no further `data` (the fence cannot attribute the drift to one specific job). All other codes — same remedies AND `data` shapes as /move/preview.
95
237
  # @param id [String] Job request ID (UUID or short_code)
96
238
  # @param job_request_move_commit_req [JobRequestMoveCommitReq] move spec
97
239
  # @param [Hash] opts the optional parameters
@@ -103,7 +245,7 @@ module Crisphive
103
245
  end
104
246
 
105
247
  # Commit a schedule-board job move
106
- # Applies the move previewed by /move/preview: places the job on the technician at the new time and pushes the displaced jobs back, atomically (per-tech advisory lock; the server recomputes the plan and fences each job on its status_version — drift since the preview returns 409 SCHEDULE_MOVE_PLAN_DRIFTED, re-preview). Same body as preview + optional &#x60;expected_version&#x60;. See SCHEDULE_BOARD_DESIGN.md. 409 NEXT STEPS: SCHEDULE_MOVE_PLAN_DRIFTED — the schedule changed since your preview (or expected_move_ids no longer match): re-preview, show the fresh plan, commit again. All other codes — same remedies as /move/preview.
248
+ # Applies the move previewed by /move/preview: places the job on the technician at the new time and pushes the displaced jobs back, atomically (per-tech advisory lock; the server recomputes the plan and fences each job on its status_version — drift since the preview returns 409 SCHEDULE_MOVE_PLAN_DRIFTED, re-preview). Same body as preview + optional &#x60;expected_version&#x60;. See SCHEDULE_BOARD_DESIGN.md. 409 NEXT STEPS: SCHEDULE_MOVE_PLAN_DRIFTED — the schedule changed since your preview (or expected_move_ids/expected_member_ids no longer match): re-preview, show the fresh plan, commit again; carries no further &#x60;data&#x60; (the fence cannot attribute the drift to one specific job). All other codes — same remedies AND &#x60;data&#x60; shapes as /move/preview.
107
249
  # @param id [String] Job request ID (UUID or short_code)
108
250
  # @param job_request_move_commit_req [JobRequestMoveCommitReq] move spec
109
251
  # @param [Hash] opts the optional parameters
@@ -168,7 +310,7 @@ module Crisphive
168
310
  end
169
311
 
170
312
  # Confirm a booking on behalf of the customer
171
- # Fires the customer-actor `confirm_booking` action from the BUSINESS surface (audited as business_on_behalf). Two uses: (1) LIVE — staff confirm a slot for a customer who booked by phone; (2) SANDBOX — the customer magic-token surface is live-only (a sandbox job's link can never reach a real customer), so this is the ONLY way to drive a sandbox test job past booking (book → quote → confirm → assign → complete). Body carries the customer-chosen scheduled_at (business-local naive datetime). DECISION TABLE — every 409 this endpoint returns, and the correct NEXT STEP (branch on error_code, never on the HTTP status): • JOB_REQUEST_STAGE_CONFLICT — the job changed since you read it (NOTE: every FAILED confirm attempt also bumps status_version by design). Next: re-GET the job, retry with the fresh status_version. • JOB_REQUEST_ACTION_NOT_PENDING — the job is no longer at the confirm step (usually: already confirmed). Next: re-GET and show current status; do not retry. • JOB_REQUEST_NO_TECHNICIAN_AVAILABLE — the TIME is infeasible for everyone (outside working hours / the customer window, or nobody qualifies). Next: pick another time via booking-windows / time-segments. NOT an emergency case — displacement cannot conjure capacity. • JOB_REQUEST_TECH_INFEASIBLE — the FORCED technician can never take the job then; `data.reason` says why: cannot_arrive_in_time (commute/shift-start — `data.earliest_feasible_at` (RFC3339 UTC) is the first same-day time they CAN be on site → offer it) | missing_required_skills | not_available_today | not_lead_tier. Next: keep the tech and reschedule to earliest_feasible_at+, OR keep the time and drop technician_id (auto-pick) / choose another tech from time-segments. NOT an emergency case. • JOB_REQUEST_P0_REQUIRES_DISPLACEMENT — the ONLY code that routes to the EMERGENCY flow: the job is P0, the tech qualifies, but the lane is genuinely occupied. Next: POST emergency/candidates → preview → commit (the commit auto-confirms). Caveat: if the occupying jobs are themselves P0 the preview will reject with EMERGENCY_RESCHEDULE_SLOT_OCCUPIED (P0 never displaces P0) — then pick another tech/time.
313
+ # Fires the customer-actor `confirm_booking` action from the BUSINESS surface (audited as business_on_behalf). Two uses: (1) LIVE — staff confirm a slot for a customer who booked by phone; (2) SANDBOX — the customer magic-token surface is live-only (a sandbox job's link can never reach a real customer), so this is the ONLY way to drive a sandbox test job past booking (book → quote → confirm → assign → complete). Body carries the customer-chosen scheduled_at (business-local naive datetime). DECISION TABLE — every 409 this endpoint returns, and the correct NEXT STEP (branch on error_code, never on the HTTP status): • JOB_REQUEST_STAGE_CONFLICT — the job changed since you read it (NOTE: every FAILED confirm attempt also bumps status_version by design). Next: re-GET the job, retry with the fresh status_version. • JOB_REQUEST_ACTION_NOT_PENDING — the job is no longer at the confirm step (usually: already confirmed). Next: re-GET and show current status; do not retry. • JOB_REQUEST_NO_TECHNICIAN_AVAILABLE — the TIME is infeasible for everyone (outside working hours / the customer window, or nobody qualifies). Best-effort `data` breakdown: `considered` (roster size checked), `blocked_by` (histogram of blockers[0].kind → count, only kinds that actually blocked someone), `truncated` (roster larger than the check covered). Next: pick another time via booking-windows / time-segments. NOT an emergency case — displacement cannot conjure capacity. • JOB_REQUEST_TECH_INFEASIBLE — the FORCED technician cannot take the job then; `data.reason` says why (blockers[0].kind): outside_service_area | missing_required_skills | not_lead_tier | no_working_day | on_time_off | off_shift | calendar_busy | calendar_conflict | visit_too_long | occupied (they are already working then — `data.earliest_feasible_at` is when they are free) | cannot_arrive_in_time (see `data.earliest_feasible_at`, RFC3339 UTC — the first same-day time they CAN be on site → offer it) | not_available_today (diagnosis unavailable). `data.blockers[]` names EVERY cause, most-structural first; `data.technician` carries `{id, name}`, name best-effort (empty only when the lookup itself could not resolve) — EXCEPT on not_lead_tier, which carries a flat `data.technician_id` and no technician object. A UI that reads only `reason` still works. Next: keep the tech and reschedule to earliest_feasible_at+, OR keep the time and drop technician_id (auto-pick) / choose another tech from time-segments. NOT an emergency case. • JOB_REQUEST_P0_REQUIRES_DISPLACEMENT — the ONLY code that routes to the EMERGENCY flow: the job is P0, the tech qualifies, but the lane is genuinely occupied. Next: POST emergency/candidates → preview → commit (the commit auto-confirms). Caveat: if the occupying jobs are themselves P0 the preview will reject with EMERGENCY_RESCHEDULE_SLOT_OCCUPIED (P0 never displaces P0) — then pick another tech/time.
172
314
  # @param id [String] Job request ID
173
315
  # @param job_request_confirm_request [JobRequestConfirmRequest] Chosen slot (scheduled_at) + optional technician_id force-assign (P0–P3 flow: pins the ranked candidate, feasibility still enforced)
174
316
  # @param [Hash] opts the optional parameters
@@ -180,7 +322,7 @@ module Crisphive
180
322
  end
181
323
 
182
324
  # Confirm a booking on behalf of the customer
183
- # Fires the customer-actor &#x60;confirm_booking&#x60; action from the BUSINESS surface (audited as business_on_behalf). Two uses: (1) LIVE — staff confirm a slot for a customer who booked by phone; (2) SANDBOX — the customer magic-token surface is live-only (a sandbox job&#39;s link can never reach a real customer), so this is the ONLY way to drive a sandbox test job past booking (book → quote → confirm → assign → complete). Body carries the customer-chosen scheduled_at (business-local naive datetime). DECISION TABLE — every 409 this endpoint returns, and the correct NEXT STEP (branch on error_code, never on the HTTP status): • JOB_REQUEST_STAGE_CONFLICT — the job changed since you read it (NOTE: every FAILED confirm attempt also bumps status_version by design). Next: re-GET the job, retry with the fresh status_version. • JOB_REQUEST_ACTION_NOT_PENDING — the job is no longer at the confirm step (usually: already confirmed). Next: re-GET and show current status; do not retry. • JOB_REQUEST_NO_TECHNICIAN_AVAILABLE — the TIME is infeasible for everyone (outside working hours / the customer window, or nobody qualifies). Next: pick another time via booking-windows / time-segments. NOT an emergency case — displacement cannot conjure capacity. • JOB_REQUEST_TECH_INFEASIBLE — the FORCED technician can never take the job then; &#x60;data.reason&#x60; says why: cannot_arrive_in_time (commute/shift-start — &#x60;data.earliest_feasible_at&#x60; (RFC3339 UTC) is the first same-day time they CAN be on site → offer it) | missing_required_skills | not_available_today | not_lead_tier. Next: keep the tech and reschedule to earliest_feasible_at+, OR keep the time and drop technician_id (auto-pick) / choose another tech from time-segments. NOT an emergency case. • JOB_REQUEST_P0_REQUIRES_DISPLACEMENT — the ONLY code that routes to the EMERGENCY flow: the job is P0, the tech qualifies, but the lane is genuinely occupied. Next: POST emergency/candidates → preview → commit (the commit auto-confirms). Caveat: if the occupying jobs are themselves P0 the preview will reject with EMERGENCY_RESCHEDULE_SLOT_OCCUPIED (P0 never displaces P0) — then pick another tech/time.
325
+ # Fires the customer-actor &#x60;confirm_booking&#x60; action from the BUSINESS surface (audited as business_on_behalf). Two uses: (1) LIVE — staff confirm a slot for a customer who booked by phone; (2) SANDBOX — the customer magic-token surface is live-only (a sandbox job&#39;s link can never reach a real customer), so this is the ONLY way to drive a sandbox test job past booking (book → quote → confirm → assign → complete). Body carries the customer-chosen scheduled_at (business-local naive datetime). DECISION TABLE — every 409 this endpoint returns, and the correct NEXT STEP (branch on error_code, never on the HTTP status): • JOB_REQUEST_STAGE_CONFLICT — the job changed since you read it (NOTE: every FAILED confirm attempt also bumps status_version by design). Next: re-GET the job, retry with the fresh status_version. • JOB_REQUEST_ACTION_NOT_PENDING — the job is no longer at the confirm step (usually: already confirmed). Next: re-GET and show current status; do not retry. • JOB_REQUEST_NO_TECHNICIAN_AVAILABLE — the TIME is infeasible for everyone (outside working hours / the customer window, or nobody qualifies). Best-effort &#x60;data&#x60; breakdown: &#x60;considered&#x60; (roster size checked), &#x60;blocked_by&#x60; (histogram of blockers[0].kind → count, only kinds that actually blocked someone), &#x60;truncated&#x60; (roster larger than the check covered). Next: pick another time via booking-windows / time-segments. NOT an emergency case — displacement cannot conjure capacity. • JOB_REQUEST_TECH_INFEASIBLE — the FORCED technician cannot take the job then; &#x60;data.reason&#x60; says why (blockers[0].kind): outside_service_area | missing_required_skills | not_lead_tier | no_working_day | on_time_off | off_shift | calendar_busy | calendar_conflict | visit_too_long | occupied (they are already working then — &#x60;data.earliest_feasible_at&#x60; is when they are free) | cannot_arrive_in_time (see &#x60;data.earliest_feasible_at&#x60;, RFC3339 UTC — the first same-day time they CAN be on site → offer it) | not_available_today (diagnosis unavailable). &#x60;data.blockers[]&#x60; names EVERY cause, most-structural first; &#x60;data.technician&#x60; carries &#x60;{id, name}&#x60;, name best-effort (empty only when the lookup itself could not resolve) — EXCEPT on not_lead_tier, which carries a flat &#x60;data.technician_id&#x60; and no technician object. A UI that reads only &#x60;reason&#x60; still works. Next: keep the tech and reschedule to earliest_feasible_at+, OR keep the time and drop technician_id (auto-pick) / choose another tech from time-segments. NOT an emergency case. • JOB_REQUEST_P0_REQUIRES_DISPLACEMENT — the ONLY code that routes to the EMERGENCY flow: the job is P0, the tech qualifies, but the lane is genuinely occupied. Next: POST emergency/candidates → preview → commit (the commit auto-confirms). Caveat: if the occupying jobs are themselves P0 the preview will reject with EMERGENCY_RESCHEDULE_SLOT_OCCUPIED (P0 never displaces P0) — then pick another tech/time.
184
326
  # @param id [String] Job request ID
185
327
  # @param job_request_confirm_request [JobRequestConfirmRequest] Chosen slot (scheduled_at) + optional technician_id force-assign (P0–P3 flow: pins the ranked candidate, feasibility still enforced)
186
328
  # @param [Hash] opts the optional parameters
@@ -245,7 +387,7 @@ module Crisphive
245
387
  end
246
388
 
247
389
  # Create a job request
248
- # Books a field-service job — the work order that enters the dispatch & scheduling pipeline. Send the customer's UUID plus requested `job_dates` (date + morning/afternoon/evening periods, ideally offered from GET /job-requests/booking-windows), optional `job_type_id` (service catalog), `skill_ids` (required technician qualifications) and a free-text description. Quoting, technician/crew assignment and completion then advance the work order through the business's workflow.
390
+ # Books a field-operations job — the work order that enters the dispatch & scheduling pipeline. Send the customer's UUID plus requested `job_dates` (date + morning/afternoon/evening periods, ideally offered from GET /job-requests/booking-windows), optional `job_type_id` (service catalog), `skill_ids` (required technician qualifications) and a free-text description. Quoting, technician/crew assignment and completion then advance the work order through the business's workflow.
249
391
  # @param job_request_create_request [JobRequestCreateRequest] Booking payload
250
392
  # @param [Hash] opts the optional parameters
251
393
  # @option opts [String] :x_timezone Customer IANA timezone
@@ -257,7 +399,7 @@ module Crisphive
257
399
  end
258
400
 
259
401
  # Create a job request
260
- # Books a field-service job — the work order that enters the dispatch &amp; scheduling pipeline. Send the customer&#39;s UUID plus requested &#x60;job_dates&#x60; (date + morning/afternoon/evening periods, ideally offered from GET /job-requests/booking-windows), optional &#x60;job_type_id&#x60; (service catalog), &#x60;skill_ids&#x60; (required technician qualifications) and a free-text description. Quoting, technician/crew assignment and completion then advance the work order through the business&#39;s workflow.
402
+ # Books a field-operations job — the work order that enters the dispatch &amp; scheduling pipeline. Send the customer&#39;s UUID plus requested &#x60;job_dates&#x60; (date + morning/afternoon/evening periods, ideally offered from GET /job-requests/booking-windows), optional &#x60;job_type_id&#x60; (service catalog), &#x60;skill_ids&#x60; (required technician qualifications) and a free-text description. Quoting, technician/crew assignment and completion then advance the work order through the business&#39;s workflow.
261
403
  # @param job_request_create_request [JobRequestCreateRequest] Booking payload
262
404
  # @param [Hash] opts the optional parameters
263
405
  # @option opts [String] :x_timezone Customer IANA timezone
@@ -319,7 +461,7 @@ module Crisphive
319
461
  end
320
462
 
321
463
  # Get a job request
322
- # Returns the full work order: current workflow status, quoted duration, confirmed schedule, customer contact snapshot and the assigned technician / crew — everything a dispatcher or an external field-service system needs to track one job.
464
+ # Returns the full work order: current workflow status, quoted duration, confirmed schedule, customer contact snapshot and the assigned technician / crew — everything a dispatcher or an external field-operations system needs to track one job.
323
465
  # @param id [String] Job request ID (UUID or short_code)
324
466
  # @param [Hash] opts the optional parameters
325
467
  # @return [GetJobRequest200Response]
@@ -329,7 +471,7 @@ module Crisphive
329
471
  end
330
472
 
331
473
  # Get a job request
332
- # Returns the full work order: current workflow status, quoted duration, confirmed schedule, customer contact snapshot and the assigned technician / crew — everything a dispatcher or an external field-service system needs to track one job.
474
+ # Returns the full work order: current workflow status, quoted duration, confirmed schedule, customer contact snapshot and the assigned technician / crew — everything a dispatcher or an external field-operations system needs to track one job.
333
475
  # @param id [String] Job request ID (UUID or short_code)
334
476
  # @param [Hash] opts the optional parameters
335
477
  # @return [Array<(GetJobRequest200Response, Integer, Hash)>] GetJobRequest200Response data, response status code and response headers
@@ -514,7 +656,7 @@ module Crisphive
514
656
  end
515
657
 
516
658
  # Matching crew candidates for a job
517
- # Technicians who can actually take this job, matched and ranked by the smart-assignment engine — skills per crew slot, weekly availability, existing schedule, time off and travel are all checked; each candidate carries a score breakdown (distance, travel, matched skills) plus the exact on-site session plan they would work. NOT a raw roster list (use GET /technicians for that). Returns the ranked feasible LEAD pool by default; pass include_buddies=true to also return per-slot buddy pools, include_vehicle=true to include the available-vehicle list. force_lead_id checks one specific technician: returns only that lead (with their crew combo) if feasible, else 409 JOB_REQUEST_NO_TECHNICIAN_AVAILABLE.
659
+ # RE-STAFFING candidates for a CONFIRMED, SCHEDULED job (not yet completed/archived) — any earlier or later stage returns 409 JOB_REQUEST_INVALID_TRANSITION. This is the pool of technicians who could REPLACE the current crew: the currently assigned lead and buddies are deliberately excluded (they are the status quo, not an option), so on a small roster an empty `leads` list is a normal answer, not an error. For pre-booking discovery (\"who could take this job before it is confirmed?\") use listJobRequestBookingWindows / listMatchingSlots / the time-segments grid instead. Candidates are matched and ranked by the smart-assignment engine — skills per crew slot, weekly availability, existing schedule, time off and travel are all checked; each carries a score breakdown (distance, travel, matched skills) plus the exact on-site session plan they would work. NOT a raw roster list (use GET /technicians for that). Returns the ranked feasible LEAD pool by default; pass include_buddies=true to also return per-slot buddy pools, include_vehicle=true to include the available-vehicle list. force_lead_id checks one specific technician: returns only that lead (with their crew combo) if feasible, else 409 JOB_REQUEST_NO_TECHNICIAN_AVAILABLE.
518
660
  # @param id [String] Job request ID or short_code
519
661
  # @param [Hash] opts the optional parameters
520
662
  # @option opts [Boolean] :include_buddies Also return buddy candidate pools
@@ -527,7 +669,7 @@ module Crisphive
527
669
  end
528
670
 
529
671
  # Matching crew candidates for a job
530
- # Technicians who can actually take this job, matched and ranked by the smart-assignment engine — skills per crew slot, weekly availability, existing schedule, time off and travel are all checked; each candidate carries a score breakdown (distance, travel, matched skills) plus the exact on-site session plan they would work. NOT a raw roster list (use GET /technicians for that). Returns the ranked feasible LEAD pool by default; pass include_buddies&#x3D;true to also return per-slot buddy pools, include_vehicle&#x3D;true to include the available-vehicle list. force_lead_id checks one specific technician: returns only that lead (with their crew combo) if feasible, else 409 JOB_REQUEST_NO_TECHNICIAN_AVAILABLE.
672
+ # RE-STAFFING candidates for a CONFIRMED, SCHEDULED job (not yet completed/archived) — any earlier or later stage returns 409 JOB_REQUEST_INVALID_TRANSITION. This is the pool of technicians who could REPLACE the current crew: the currently assigned lead and buddies are deliberately excluded (they are the status quo, not an option), so on a small roster an empty &#x60;leads&#x60; list is a normal answer, not an error. For pre-booking discovery (\&quot;who could take this job before it is confirmed?\&quot;) use listJobRequestBookingWindows / listMatchingSlots / the time-segments grid instead. Candidates are matched and ranked by the smart-assignment engine — skills per crew slot, weekly availability, existing schedule, time off and travel are all checked; each carries a score breakdown (distance, travel, matched skills) plus the exact on-site session plan they would work. NOT a raw roster list (use GET /technicians for that). Returns the ranked feasible LEAD pool by default; pass include_buddies&#x3D;true to also return per-slot buddy pools, include_vehicle&#x3D;true to include the available-vehicle list. force_lead_id checks one specific technician: returns only that lead (with their crew combo) if feasible, else 409 JOB_REQUEST_NO_TECHNICIAN_AVAILABLE.
531
673
  # @param id [String] Job request ID or short_code
532
674
  # @param [Hash] opts the optional parameters
533
675
  # @option opts [Boolean] :include_buddies Also return buddy candidate pools
@@ -586,7 +728,7 @@ module Crisphive
586
728
  end
587
729
 
588
730
  # Rank technicians for a P0 emergency insert
589
- # Returns the technicians who could take the emergency job at the requested start, ranked FASTEST-ARRIVAL first (arrival beats route efficiency for a P0). The response also carries a historical `crew_recommendation` (median crew size on comparable completed jobs + mandatory disclaimer — AC-2). Booked technicians are still candidates — each entry carries the displacement preview (which lower-priority jobs would be pushed, per day) that committing to them would cause; total_moves=0 means a free slot. P0 jobs are never displaced; P1 only by a P0. ETA is estimated from the technician's start location (no live GPS). Feed the chosen technician_id into emergency/preview + emergency/commit. 409 NEXT STEPS: EMERGENCY_RESCHEDULE_NOT_ELIGIBLE — the job cannot be emergency-inserted (not P0, already started/completed/archived, or not quoted): fix the job state or use a normal confirm. EMERGENCY_RESCHEDULE_CREW_UNSUPPORTED — crew jobs cannot use the emergency flow (v1): staff via confirm/reassign instead. EMERGENCY_RESCHEDULE_MULTIDAY_UNSUPPORTED — a confirmed multi-day job cannot be re-inserted (v1): use the normal reassign flow. EMERGENCY_RESCHEDULE_NO_WORKING_DAY — the chosen date has no working hours: pick a working day. EMERGENCY_RESCHEDULE_IN_PAST — start time already passed: pick a future time.
731
+ # Returns the technicians who could take the emergency job at the requested start, ranked FASTEST-ARRIVAL first (arrival beats route efficiency for a P0). The response also carries a historical `crew_recommendation` (median crew size on comparable completed jobs + mandatory disclaimer — AC-2). Booked technicians are still candidates — each entry carries the displacement preview (which lower-priority jobs would be pushed, per day) that committing to them would cause; total_moves=0 means a free slot. P0 jobs are never displaced; P1 only by a P0. ETA is estimated from the technician's start location (no live GPS). `after_hours_override=true` — the coordinator has phoned the technician — drops the non-working-day rejection AND each candidate's working-hours/time-off feasibility check; an affected candidate carries a per-technician TIME_OFF_OVERLAP warning instead. This is THE phone list for an after-hours insert: it ranks even on a day with no working hours once the flag is set. Feed the chosen technician_id into emergency/preview + emergency/commit. 409 NEXT STEPS: EMERGENCY_RESCHEDULE_NOT_ELIGIBLE — the job cannot be emergency-inserted; `data.failed_precondition` names which (not_quoted | archived | completed | not_p0 | smart_assign_unavailable): fix the job state or use a normal confirm. EMERGENCY_RESCHEDULE_CREW_UNSUPPORTED — crew jobs cannot use the emergency flow (v1, `data.crew_size` = lead + buddies): staff via confirm/reassign instead. EMERGENCY_RESCHEDULE_MULTIDAY_UNSUPPORTED — either a confirmed multi-day job (`data.session_count`/`data.session_dates`; use the normal reassign flow) or a single visit longer than the structural span bound, in which case `data.reason=visit_too_long` + `data.blockers[0]` (visit_minutes/max_minutes) name it — no remedy but a shorter visit. EMERGENCY_RESCHEDULE_NO_WORKING_DAY — the chosen date has no working hours (`data.business_timezone`/`data.requested_weekday`): pick a working day, or set after_hours_override=true (the coordinator has phoned someone) to rank candidates anyway. EMERGENCY_RESCHEDULE_IN_PAST — start time already passed: pick a future time; `data` carries the timezone the naive start_at was read in (`business_timezone`) plus the instant it resolved to, so a start that looks future on the caller's own clock can be diagnosed without guessing.
590
732
  # @param job_request_emergency_candidates_request [JobRequestEmergencyCandidatesRequest] Emergency job + desired start
591
733
  # @param [Hash] opts the optional parameters
592
734
  # @return [ListEmergencyCandidates200Response]
@@ -596,7 +738,7 @@ module Crisphive
596
738
  end
597
739
 
598
740
  # Rank technicians for a P0 emergency insert
599
- # Returns the technicians who could take the emergency job at the requested start, ranked FASTEST-ARRIVAL first (arrival beats route efficiency for a P0). The response also carries a historical &#x60;crew_recommendation&#x60; (median crew size on comparable completed jobs + mandatory disclaimer — AC-2). Booked technicians are still candidates — each entry carries the displacement preview (which lower-priority jobs would be pushed, per day) that committing to them would cause; total_moves&#x3D;0 means a free slot. P0 jobs are never displaced; P1 only by a P0. ETA is estimated from the technician&#39;s start location (no live GPS). Feed the chosen technician_id into emergency/preview + emergency/commit. 409 NEXT STEPS: EMERGENCY_RESCHEDULE_NOT_ELIGIBLE — the job cannot be emergency-inserted (not P0, already started/completed/archived, or not quoted): fix the job state or use a normal confirm. EMERGENCY_RESCHEDULE_CREW_UNSUPPORTED — crew jobs cannot use the emergency flow (v1): staff via confirm/reassign instead. EMERGENCY_RESCHEDULE_MULTIDAY_UNSUPPORTED — a confirmed multi-day job cannot be re-inserted (v1): use the normal reassign flow. EMERGENCY_RESCHEDULE_NO_WORKING_DAY — the chosen date has no working hours: pick a working day. EMERGENCY_RESCHEDULE_IN_PAST — start time already passed: pick a future time.
741
+ # Returns the technicians who could take the emergency job at the requested start, ranked FASTEST-ARRIVAL first (arrival beats route efficiency for a P0). The response also carries a historical &#x60;crew_recommendation&#x60; (median crew size on comparable completed jobs + mandatory disclaimer — AC-2). Booked technicians are still candidates — each entry carries the displacement preview (which lower-priority jobs would be pushed, per day) that committing to them would cause; total_moves&#x3D;0 means a free slot. P0 jobs are never displaced; P1 only by a P0. ETA is estimated from the technician&#39;s start location (no live GPS). &#x60;after_hours_override&#x3D;true&#x60; — the coordinator has phoned the technician — drops the non-working-day rejection AND each candidate&#39;s working-hours/time-off feasibility check; an affected candidate carries a per-technician TIME_OFF_OVERLAP warning instead. This is THE phone list for an after-hours insert: it ranks even on a day with no working hours once the flag is set. Feed the chosen technician_id into emergency/preview + emergency/commit. 409 NEXT STEPS: EMERGENCY_RESCHEDULE_NOT_ELIGIBLE — the job cannot be emergency-inserted; &#x60;data.failed_precondition&#x60; names which (not_quoted | archived | completed | not_p0 | smart_assign_unavailable): fix the job state or use a normal confirm. EMERGENCY_RESCHEDULE_CREW_UNSUPPORTED — crew jobs cannot use the emergency flow (v1, &#x60;data.crew_size&#x60; &#x3D; lead + buddies): staff via confirm/reassign instead. EMERGENCY_RESCHEDULE_MULTIDAY_UNSUPPORTED — either a confirmed multi-day job (&#x60;data.session_count&#x60;/&#x60;data.session_dates&#x60;; use the normal reassign flow) or a single visit longer than the structural span bound, in which case &#x60;data.reason&#x3D;visit_too_long&#x60; + &#x60;data.blockers[0]&#x60; (visit_minutes/max_minutes) name it — no remedy but a shorter visit. EMERGENCY_RESCHEDULE_NO_WORKING_DAY — the chosen date has no working hours (&#x60;data.business_timezone&#x60;/&#x60;data.requested_weekday&#x60;): pick a working day, or set after_hours_override&#x3D;true (the coordinator has phoned someone) to rank candidates anyway. EMERGENCY_RESCHEDULE_IN_PAST — start time already passed: pick a future time; &#x60;data&#x60; carries the timezone the naive start_at was read in (&#x60;business_timezone&#x60;) plus the instant it resolved to, so a start that looks future on the caller&#39;s own clock can be diagnosed without guessing.
600
742
  # @param job_request_emergency_candidates_request [JobRequestEmergencyCandidatesRequest] Emergency job + desired start
601
743
  # @param [Hash] opts the optional parameters
602
744
  # @return [Array<(ListEmergencyCandidates200Response, Integer, Hash)>] ListEmergencyCandidates200Response data, response status code and response headers
@@ -724,7 +866,7 @@ module Crisphive
724
866
  end
725
867
 
726
868
  # Poll for new & changed job requests (sync feed)
727
- # Keep an external system (your CRM, ERP or field-service tool) in sync with bookings WITHOUT re-listing everything: returns the job requests (work orders) whose state changed (created, status transition, reschedule, soft-delete/archive) at or after the `since` cursor, ordered oldest-change-first (updated_at ASC). How to use it: (1) On your first poll OMIT `since` — the server primes the cursor at \"now\", returns no items and a `next_since`. (2) Store `next_since` and pass it as `since` on the next poll. (3) Apply each returned item to your store by UPSERTING on `id` (the server re-scans a ~5s safety window, so the same job may appear again — never blindly append). (4) If `has_more` is true the page filled to `limit` and more changes are already waiting — poll again immediately; otherwise wait your normal interval (e.g. 5–15s). This is NOT pagination — it is a time-keyed change feed. Use the paginated GET /job-requests for the initial bulk load, then this endpoint to stay live. Filters (status_keys, customer_id, …) narrow the feed to the slice you care about.
869
+ # Keep an external system (your CRM, ERP or field-operations tool) in sync with bookings WITHOUT re-listing everything: returns the job requests (work orders) whose state changed (created, status transition, reschedule, soft-delete/archive) at or after the `since` cursor, ordered oldest-change-first (updated_at ASC). How to use it: (1) On your first poll OMIT `since` — the server primes the cursor at \"now\", returns no items and a `next_since`. (2) Store `next_since` and pass it as `since` on the next poll. (3) Apply each returned item to your store by UPSERTING on `id` (the server re-scans a ~5s safety window, so the same job may appear again — never blindly append). (4) If `has_more` is true the page filled to `limit` and more changes are already waiting — poll again immediately; otherwise wait your normal interval (e.g. 5–15s). This is NOT pagination — it is a time-keyed change feed. Use the paginated GET /job-requests for the initial bulk load, then this endpoint to stay live. Filters (status_keys, customer_id, …) narrow the feed to the slice you care about.
728
870
  # @param [Hash] opts the optional parameters
729
871
  # @option opts [String] :status_keys Comma-separated status slugs — only surface changes to jobs in these statuses
730
872
  # @option opts [String] :priority Priority filter (p0|p1|p2|p3)
@@ -741,7 +883,7 @@ module Crisphive
741
883
  end
742
884
 
743
885
  # Poll for new &amp; changed job requests (sync feed)
744
- # Keep an external system (your CRM, ERP or field-service tool) in sync with bookings WITHOUT re-listing everything: returns the job requests (work orders) whose state changed (created, status transition, reschedule, soft-delete/archive) at or after the &#x60;since&#x60; cursor, ordered oldest-change-first (updated_at ASC). How to use it: (1) On your first poll OMIT &#x60;since&#x60; — the server primes the cursor at \&quot;now\&quot;, returns no items and a &#x60;next_since&#x60;. (2) Store &#x60;next_since&#x60; and pass it as &#x60;since&#x60; on the next poll. (3) Apply each returned item to your store by UPSERTING on &#x60;id&#x60; (the server re-scans a ~5s safety window, so the same job may appear again — never blindly append). (4) If &#x60;has_more&#x60; is true the page filled to &#x60;limit&#x60; and more changes are already waiting — poll again immediately; otherwise wait your normal interval (e.g. 5–15s). This is NOT pagination — it is a time-keyed change feed. Use the paginated GET /job-requests for the initial bulk load, then this endpoint to stay live. Filters (status_keys, customer_id, …) narrow the feed to the slice you care about.
886
+ # Keep an external system (your CRM, ERP or field-operations tool) in sync with bookings WITHOUT re-listing everything: returns the job requests (work orders) whose state changed (created, status transition, reschedule, soft-delete/archive) at or after the &#x60;since&#x60; cursor, ordered oldest-change-first (updated_at ASC). How to use it: (1) On your first poll OMIT &#x60;since&#x60; — the server primes the cursor at \&quot;now\&quot;, returns no items and a &#x60;next_since&#x60;. (2) Store &#x60;next_since&#x60; and pass it as &#x60;since&#x60; on the next poll. (3) Apply each returned item to your store by UPSERTING on &#x60;id&#x60; (the server re-scans a ~5s safety window, so the same job may appear again — never blindly append). (4) If &#x60;has_more&#x60; is true the page filled to &#x60;limit&#x60; and more changes are already waiting — poll again immediately; otherwise wait your normal interval (e.g. 5–15s). This is NOT pagination — it is a time-keyed change feed. Use the paginated GET /job-requests for the initial bulk load, then this endpoint to stay live. Filters (status_keys, customer_id, …) narrow the feed to the slice you care about.
745
887
  # @param [Hash] opts the optional parameters
746
888
  # @option opts [String] :status_keys Comma-separated status slugs — only surface changes to jobs in these statuses
747
889
  # @option opts [String] :priority Priority filter (p0|p1|p2|p3)
@@ -815,6 +957,8 @@ module Crisphive
815
957
  # @option opts [String] :service_area_id Service-area UUID (board zone filter)
816
958
  # @option opts [String] :scheduled_from Filter from (YYYY-MM-DD &#x3D; start of that day in the business timezone, or RFC3339); range is [from, to)
817
959
  # @option opts [String] :scheduled_to Filter to (YYYY-MM-DD &#x3D; end of that day in the business timezone, or RFC3339), exclusive
960
+ # @option opts [String] :completed_from Filter by completion time from (YYYY-MM-DD &#x3D; start of that day in the business timezone, or RFC3339); range is [from, to). Counts jobs by when they were completed regardless of reschedules.
961
+ # @option opts [String] :completed_to Filter by completion time to (YYYY-MM-DD &#x3D; end of that day in the business timezone, or RFC3339), exclusive
818
962
  # @option opts [String] :q Search short_code or description (case-insensitive, partial match)
819
963
  # @option opts [String] :sort Sort key: created_at:desc (default) | created_at:asc | scheduled_at:asc | scheduled_at:desc | priority:asc (P0 first) | priority:desc
820
964
  # @option opts [Integer] :page Page number
@@ -836,6 +980,8 @@ module Crisphive
836
980
  # @option opts [String] :service_area_id Service-area UUID (board zone filter)
837
981
  # @option opts [String] :scheduled_from Filter from (YYYY-MM-DD &#x3D; start of that day in the business timezone, or RFC3339); range is [from, to)
838
982
  # @option opts [String] :scheduled_to Filter to (YYYY-MM-DD &#x3D; end of that day in the business timezone, or RFC3339), exclusive
983
+ # @option opts [String] :completed_from Filter by completion time from (YYYY-MM-DD &#x3D; start of that day in the business timezone, or RFC3339); range is [from, to). Counts jobs by when they were completed regardless of reschedules.
984
+ # @option opts [String] :completed_to Filter by completion time to (YYYY-MM-DD &#x3D; end of that day in the business timezone, or RFC3339), exclusive
839
985
  # @option opts [String] :q Search short_code or description (case-insensitive, partial match)
840
986
  # @option opts [String] :sort Sort key: created_at:desc (default) | created_at:asc | scheduled_at:asc | scheduled_at:desc | priority:asc (P0 first) | priority:desc
841
987
  # @option opts [Integer] :page Page number
@@ -858,6 +1004,8 @@ module Crisphive
858
1004
  query_params[:'service_area_id'] = opts[:'service_area_id'] if !opts[:'service_area_id'].nil?
859
1005
  query_params[:'scheduled_from'] = opts[:'scheduled_from'] if !opts[:'scheduled_from'].nil?
860
1006
  query_params[:'scheduled_to'] = opts[:'scheduled_to'] if !opts[:'scheduled_to'].nil?
1007
+ query_params[:'completed_from'] = opts[:'completed_from'] if !opts[:'completed_from'].nil?
1008
+ query_params[:'completed_to'] = opts[:'completed_to'] if !opts[:'completed_to'].nil?
861
1009
  query_params[:'q'] = opts[:'q'] if !opts[:'q'].nil?
862
1010
  query_params[:'sort'] = opts[:'sort'] if !opts[:'sort'].nil?
863
1011
  query_params[:'page'] = opts[:'page'] if !opts[:'page'].nil?
@@ -1046,8 +1194,76 @@ module Crisphive
1046
1194
  return data, status_code, headers
1047
1195
  end
1048
1196
 
1197
+ # Preview re-staffing a technician's whole day (sick call)
1198
+ # Solves (WITHOUT writing) the re-staffing of every job on a technician's board for a date range: each job is handed to an ALTERNATE lead technician at its UNCHANGED window — the customer's appointment never moves, two overlapping jobs never land on the same alternate, and the absent technician is never a candidate. `date`/`until_date` are business-local calendar days (inclusive, ≤ 14 days). Jobs the planner cannot re-staff come back in `unresolved` with a `reason_code` (no qualified technician free / crew job / multi-day job / already in progress) — a partial plan is a normal 200, not an error. `solver.duration_ms` is server-side planner time; `solver.deterministic` is true (same input ⇒ same plan). Read-only, safe to repeat; copy `resolved[]` into the commit body. No time-off record is required to preview. When the strict pass leaves a job unresolved, its row ALSO carries `alternatives[]` — the relaxation ladder's priced options, cheapest constraint first (ABSENCE_RESOLVE_DESIGN.md §9): `reassign_out_of_area` (same window, a lead outside the job's zone — cost.distance_km/travel_minutes), then `reschedule_same_day` / `reschedule_later_day` (the earliest free window on a qualified lead, in-area before out-of-area, up to 3 working days past until_date — cost.customer_renotified, cost.day_offset, cost.sla_breached). Each option is a PROPOSAL: nothing is applied until the coordinator copies it into the commit body with its `alternative_kind` (+ `start_at`/`end_at` for a reschedule). `alternatives` is an empty array when even the ladder found nothing; `solver.alternatives_truncated` is true when the ladder's time budget cut the search short. Displacing another customer's job and overtime are deliberately NOT offered. See ABSENCE_RESOLVE_DESIGN.md.
1199
+ # @param job_request_absence_preview_request [JobRequestAbsencePreviewRequest] absent technician + date range (+ optional job subset)
1200
+ # @param [Hash] opts the optional parameters
1201
+ # @return [CommitAbsenceResolve200Response]
1202
+ def preview_absence_resolve(job_request_absence_preview_request, opts = {})
1203
+ data, _status_code, _headers = preview_absence_resolve_with_http_info(job_request_absence_preview_request, opts)
1204
+ data
1205
+ end
1206
+
1207
+ # Preview re-staffing a technician&#39;s whole day (sick call)
1208
+ # Solves (WITHOUT writing) the re-staffing of every job on a technician&#39;s board for a date range: each job is handed to an ALTERNATE lead technician at its UNCHANGED window — the customer&#39;s appointment never moves, two overlapping jobs never land on the same alternate, and the absent technician is never a candidate. &#x60;date&#x60;/&#x60;until_date&#x60; are business-local calendar days (inclusive, ≤ 14 days). Jobs the planner cannot re-staff come back in &#x60;unresolved&#x60; with a &#x60;reason_code&#x60; (no qualified technician free / crew job / multi-day job / already in progress) — a partial plan is a normal 200, not an error. &#x60;solver.duration_ms&#x60; is server-side planner time; &#x60;solver.deterministic&#x60; is true (same input ⇒ same plan). Read-only, safe to repeat; copy &#x60;resolved[]&#x60; into the commit body. No time-off record is required to preview. When the strict pass leaves a job unresolved, its row ALSO carries &#x60;alternatives[]&#x60; — the relaxation ladder&#39;s priced options, cheapest constraint first (ABSENCE_RESOLVE_DESIGN.md §9): &#x60;reassign_out_of_area&#x60; (same window, a lead outside the job&#39;s zone — cost.distance_km/travel_minutes), then &#x60;reschedule_same_day&#x60; / &#x60;reschedule_later_day&#x60; (the earliest free window on a qualified lead, in-area before out-of-area, up to 3 working days past until_date — cost.customer_renotified, cost.day_offset, cost.sla_breached). Each option is a PROPOSAL: nothing is applied until the coordinator copies it into the commit body with its &#x60;alternative_kind&#x60; (+ &#x60;start_at&#x60;/&#x60;end_at&#x60; for a reschedule). &#x60;alternatives&#x60; is an empty array when even the ladder found nothing; &#x60;solver.alternatives_truncated&#x60; is true when the ladder&#39;s time budget cut the search short. Displacing another customer&#39;s job and overtime are deliberately NOT offered. See ABSENCE_RESOLVE_DESIGN.md.
1209
+ # @param job_request_absence_preview_request [JobRequestAbsencePreviewRequest] absent technician + date range (+ optional job subset)
1210
+ # @param [Hash] opts the optional parameters
1211
+ # @return [Array<(CommitAbsenceResolve200Response, Integer, Hash)>] CommitAbsenceResolve200Response data, response status code and response headers
1212
+ def preview_absence_resolve_with_http_info(job_request_absence_preview_request, opts = {})
1213
+ if @api_client.config.debugging
1214
+ @api_client.config.logger.debug 'Calling API: JobRequestBusinessApi.preview_absence_resolve ...'
1215
+ end
1216
+ # verify the required parameter 'job_request_absence_preview_request' is set
1217
+ if @api_client.config.client_side_validation && job_request_absence_preview_request.nil?
1218
+ fail ArgumentError, "Missing the required parameter 'job_request_absence_preview_request' when calling JobRequestBusinessApi.preview_absence_resolve"
1219
+ end
1220
+ # resource path
1221
+ local_var_path = '/job-requests/absence/preview'
1222
+
1223
+ # query parameters
1224
+ query_params = opts[:query_params] || {}
1225
+
1226
+ # header parameters
1227
+ header_params = opts[:header_params] || {}
1228
+ # HTTP header 'Accept' (if needed)
1229
+ header_params['Accept'] = @api_client.select_header_accept(['application/json']) unless header_params['Accept']
1230
+ # HTTP header 'Content-Type'
1231
+ content_type = @api_client.select_header_content_type(['application/json'])
1232
+ if !content_type.nil?
1233
+ header_params['Content-Type'] = content_type
1234
+ end
1235
+
1236
+ # form parameters
1237
+ form_params = opts[:form_params] || {}
1238
+
1239
+ # http body (model)
1240
+ post_body = opts[:debug_body] || @api_client.object_to_http_body(job_request_absence_preview_request)
1241
+
1242
+ # return_type
1243
+ return_type = opts[:debug_return_type] || 'CommitAbsenceResolve200Response'
1244
+
1245
+ # auth_names
1246
+ auth_names = opts[:debug_auth_names] || ['ApiKeyAuth']
1247
+
1248
+ new_options = opts.merge(
1249
+ :operation => :"JobRequestBusinessApi.preview_absence_resolve",
1250
+ :header_params => header_params,
1251
+ :query_params => query_params,
1252
+ :form_params => form_params,
1253
+ :body => post_body,
1254
+ :auth_names => auth_names,
1255
+ :return_type => return_type
1256
+ )
1257
+
1258
+ data, status_code, headers = @api_client.call_api(:POST, local_var_path, new_options)
1259
+ if @api_client.config.debugging
1260
+ @api_client.config.logger.debug "API called: JobRequestBusinessApi#preview_absence_resolve\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"
1261
+ end
1262
+ return data, status_code, headers
1263
+ end
1264
+
1049
1265
  # Preview emergency insert + cascade reschedule
1050
- # Computes (WITHOUT writing) the cascade of inserting an emergency job onto a technician at a chosen time: where the emergency lands + every job pushed back, grouped per business-local day. `displacement_mode=reassign` instead hands each displaced job to another feasible technician at its ORIGINAL window (same-day promise) — jobs with no alternate capacity fall back to reschedule and stay in `days`. `mode=overtime` keeps everyone same-day (tech works late); `mode=next_day` rolls overflow to the next working day(s). Read-only — safe to call repeatedly; commit is a separate endpoint. Isolated feature (see EMERGENCY_RESCHEDULE_DESIGN.md). 409 NEXT STEPS: EMERGENCY_RESCHEDULE_SLOT_OCCUPIED — the landing window is blocked by a job the cascade may NOT move (another P0, a crew or multi-day job): choose another technician (walk the /candidates ranking) or another time; displacement never touches P0/crew/multi-day anchors. EMERGENCY_RESCHEDULE_NOT_ELIGIBLE / CREW_UNSUPPORTED / MULTIDAY_UNSUPPORTED / NO_WORKING_DAY / IN_PAST — same remedies as /candidates.
1266
+ # Computes (WITHOUT writing) the cascade of inserting an emergency job onto a technician at a chosen time: where the emergency lands + every job pushed back, grouped per business-local day. `displacement_mode=reassign` instead hands each displaced job to another feasible technician at its ORIGINAL window (same-day promise) — jobs with no alternate capacity fall back to reschedule and stay in `days`. `mode=overtime` keeps everyone same-day (tech works late); `mode=next_day` rolls overflow to the next working day(s). On a day the business does not work the preview does NOT need `after_hours_override`: it plans as the override would and returns `after_hours_override_required: true` plus an AFTER_HOURS warning — ask the coordinator whether they have phoned the technician, then commit with after_hours_override=true (the commit refuses without it). This endpoint never runs the technician's working-hours/time-off feasibility check (that only happens on /candidates) — it validates the named technician exists and builds the cascade. Read-only — safe to call repeatedly; commit is a separate endpoint. Isolated feature (see EMERGENCY_RESCHEDULE_DESIGN.md). 409 NEXT STEPS: EMERGENCY_RESCHEDULE_SLOT_OCCUPIED — the landing window is blocked by a job the cascade may NOT move (another P0, a crew or multi-day job); `data.conflicts[]` names each blocking job (short_code/start_at/end_at/frozen_because): choose another technician (walk the /candidates ranking) or another time; displacement never touches P0/crew/multi-day anchors. EMERGENCY_RESCHEDULE_NOT_ELIGIBLE / CREW_UNSUPPORTED / MULTIDAY_UNSUPPORTED / IN_PAST — same remedies and `data` shapes as /candidates. EMERGENCY_RESCHEDULE_NO_WORKING_DAY is never returned by the preview: since 2026-10-03 a closed day answers 200 with after_hours_override_required=true instead.
1051
1267
  # @param job_request_emergency_preview_request [JobRequestEmergencyPreviewRequest] emergency insert spec
1052
1268
  # @param [Hash] opts the optional parameters
1053
1269
  # @return [CommitEmergencyReschedule200Response]
@@ -1057,7 +1273,7 @@ module Crisphive
1057
1273
  end
1058
1274
 
1059
1275
  # Preview emergency insert + cascade reschedule
1060
- # Computes (WITHOUT writing) the cascade of inserting an emergency job onto a technician at a chosen time: where the emergency lands + every job pushed back, grouped per business-local day. &#x60;displacement_mode&#x3D;reassign&#x60; instead hands each displaced job to another feasible technician at its ORIGINAL window (same-day promise) — jobs with no alternate capacity fall back to reschedule and stay in &#x60;days&#x60;. &#x60;mode&#x3D;overtime&#x60; keeps everyone same-day (tech works late); &#x60;mode&#x3D;next_day&#x60; rolls overflow to the next working day(s). Read-only — safe to call repeatedly; commit is a separate endpoint. Isolated feature (see EMERGENCY_RESCHEDULE_DESIGN.md). 409 NEXT STEPS: EMERGENCY_RESCHEDULE_SLOT_OCCUPIED — the landing window is blocked by a job the cascade may NOT move (another P0, a crew or multi-day job): choose another technician (walk the /candidates ranking) or another time; displacement never touches P0/crew/multi-day anchors. EMERGENCY_RESCHEDULE_NOT_ELIGIBLE / CREW_UNSUPPORTED / MULTIDAY_UNSUPPORTED / NO_WORKING_DAY / IN_PAST — same remedies as /candidates.
1276
+ # Computes (WITHOUT writing) the cascade of inserting an emergency job onto a technician at a chosen time: where the emergency lands + every job pushed back, grouped per business-local day. &#x60;displacement_mode&#x3D;reassign&#x60; instead hands each displaced job to another feasible technician at its ORIGINAL window (same-day promise) — jobs with no alternate capacity fall back to reschedule and stay in &#x60;days&#x60;. &#x60;mode&#x3D;overtime&#x60; keeps everyone same-day (tech works late); &#x60;mode&#x3D;next_day&#x60; rolls overflow to the next working day(s). On a day the business does not work the preview does NOT need &#x60;after_hours_override&#x60;: it plans as the override would and returns &#x60;after_hours_override_required: true&#x60; plus an AFTER_HOURS warning — ask the coordinator whether they have phoned the technician, then commit with after_hours_override&#x3D;true (the commit refuses without it). This endpoint never runs the technician&#39;s working-hours/time-off feasibility check (that only happens on /candidates) — it validates the named technician exists and builds the cascade. Read-only — safe to call repeatedly; commit is a separate endpoint. Isolated feature (see EMERGENCY_RESCHEDULE_DESIGN.md). 409 NEXT STEPS: EMERGENCY_RESCHEDULE_SLOT_OCCUPIED — the landing window is blocked by a job the cascade may NOT move (another P0, a crew or multi-day job); &#x60;data.conflicts[]&#x60; names each blocking job (short_code/start_at/end_at/frozen_because): choose another technician (walk the /candidates ranking) or another time; displacement never touches P0/crew/multi-day anchors. EMERGENCY_RESCHEDULE_NOT_ELIGIBLE / CREW_UNSUPPORTED / MULTIDAY_UNSUPPORTED / IN_PAST — same remedies and &#x60;data&#x60; shapes as /candidates. EMERGENCY_RESCHEDULE_NO_WORKING_DAY is never returned by the preview: since 2026-10-03 a closed day answers 200 with after_hours_override_required&#x3D;true instead.
1061
1277
  # @param job_request_emergency_preview_request [JobRequestEmergencyPreviewRequest] emergency insert spec
1062
1278
  # @param [Hash] opts the optional parameters
1063
1279
  # @return [Array<(CommitEmergencyReschedule200Response, Integer, Hash)>] CommitEmergencyReschedule200Response data, response status code and response headers
@@ -1115,7 +1331,7 @@ module Crisphive
1115
1331
  end
1116
1332
 
1117
1333
  # Preview a schedule-board job move
1118
- # Computes (WITHOUT writing) the outcome of moving a confirmed job to a new time and/or technician: where it lands, every later job pushed back per `mode`, and the warnings the coordinator would accept (displaced jobs leaving their confirmed windows, overtime). Same technician = pure time move; different technician = manual reassign. Read-only — safe to call repeatedly while dragging; commit is a separate endpoint. See SCHEDULE_BOARD_DESIGN.md. Warning detail: a TECH_NOT_FEASIBLE warning carries `reason` = `cannot_arrive_in_time` (commute from the tech day-start location / shift start; `earliest_feasible_at` (RFC3339 UTC) is the first same-day time they CAN be on site — suggest it as the drop slot) | `missing_required_skills` | `not_available_today` (no working hours, approved time off, or outside the service area) | `not_lead_tier`. For a P0 move this warning is advisory (coordinator may commit anyway); for p1/p2/p3 the same condition is the hard 409 SCHEDULE_MOVE_TECH_INFEASIBLE. 409 NEXT STEPS: SCHEDULE_MOVE_NOT_ELIGIBLE (job unconfirmed/unquoted/archived/completed — not movable) · SCHEDULE_MOVE_IN_PROGRESS (tech already executing — do not move) · SCHEDULE_MOVE_IN_PAST (pick a future time) · SCHEDULE_MOVE_OUTSIDE_WINDOW (landing time outside the customer-confirmed window — hard block; pick a time inside it) · SCHEDULE_MOVE_SLOT_OCCUPIED (landing window blocked by an immovable anchor — another tech/time) · SCHEDULE_MOVE_TECH_INFEASIBLE (non-P0 hard block: target tech not qualified/available — see the TECH_NOT_FEASIBLE warning reasons; change tech or time) · SCHEDULE_MOVE_MULTIDAY_UNSUPPORTED (multi-day jobs not movable v1) · SCHEDULE_MOVE_NO_WORKING_DAY (pick a working day) · SCHEDULE_MOVE_REQUIRES_FREE_SLOT (non-P0 moves may not displace — free capacity only, unless the owner enables allow_non_p0_displacement) · SCHEDULE_MOVE_CREW_UNSTAFFABLE (a crew slot has no feasible replacement at the new time — another time).
1334
+ # Computes (WITHOUT writing) the outcome of moving a confirmed job to a new time and/or technician: where it lands, every later job pushed back per `mode`, and the warnings the coordinator would accept (displaced jobs leaving their confirmed windows, overtime). Same technician = pure time move; different technician = manual reassign. Read-only — safe to call repeatedly while dragging; commit is a separate endpoint. On a day the business does not work, a p0 single-person job previews WITHOUT after_hours_override: the plan is computed as the override would place it and the response carries `after_hours_override_required: true` — ask the coordinator whether they have phoned the technician, then commit with after_hours_override=true. Any other job is refused with SCHEDULE_MOVE_NO_WORKING_DAY and `data.override_available: false`. See SCHEDULE_BOARD_DESIGN.md. Warning detail: a TECH_NOT_FEASIBLE warning carries `reason` (= `blockers[0].kind`) = `outside_service_area` | `missing_required_skills` | `not_lead_tier` | `no_working_day` | `on_time_off` | `off_shift` | `visit_too_long` | `cannot_arrive_in_time` (commute from the tech day-start location / shift start; `earliest_feasible_at`, RFC3339 UTC, is the first same-day time they CAN be on site — suggest it as the drop slot) | `not_available_today` (diagnosis unavailable). `blockers[]` names EVERY hard filter that failed, most-structural first — a client reading only `reason` still works. For a P0 move this warning is advisory (coordinator may commit anyway) and carries the SAME `blockers[]` a p1/p2/p3 move would get as the hard 409 SCHEDULE_MOVE_TECH_INFEASIBLE below. 409 NEXT STEPS: SCHEDULE_MOVE_NOT_ELIGIBLE (job unconfirmed/unquoted/archived/completed — `data.failed_precondition` names which) · SCHEDULE_MOVE_IN_PROGRESS (tech already executing — `data.fired_actions[]` lists the actions already fired; do not move) · SCHEDULE_MOVE_IN_PAST (pick a future time — its `data` carries `business_timezone`, the naive `start_at` and the `start_at_utc` it resolved to, which is what tells a caller whose own clock says otherwise where the difference came from) · SCHEDULE_MOVE_SLOT_OCCUPIED (landing window blocked by an immovable anchor; `data.conflicts[]` names it — another tech/time) · SCHEDULE_MOVE_TECH_INFEASIBLE (non-P0 hard block: target tech not qualified/available — its `data` carries `technician` (id+name), `reason` (same catalog as the TECH_NOT_FEASIBLE warning above), `blockers[]` (every cause, most-structural first) and, for `cannot_arrive_in_time`, `earliest_feasible_at` (RFC3339 UTC) to suggest as the drop slot; change tech or time) · SCHEDULE_MOVE_MULTIDAY_UNSUPPORTED (multi-day jobs not movable v1 — `data.session_count`/`data.session_dates`) · SCHEDULE_MOVE_NO_WORKING_DAY (`data.business_timezone`/`data.requested_weekday` — pick a working day, or set after_hours_override=true for a P0 whose technician has been phoned) · SCHEDULE_MOVE_REQUIRES_FREE_SLOT (non-P0 moves may not displace — `data.would_push[]` names the jobs that would be pushed, `data.allow_non_p0_displacement: false` names the setting that would permit it, unless the crew case sets `data.crew_never_displaces: true` instead — free capacity only) · SCHEDULE_MOVE_CREW_UNSTAFFABLE (a crew slot has no feasible replacement at the new time — another time). A landing outside the customer-confirmed window is NOT an error — it returns 200 with a MOVED_OUTSIDE_WINDOW warning (customer_window attached) that the coordinator overrides.
1119
1335
  # @param id [String] Job request ID (UUID or short_code)
1120
1336
  # @param job_request_move_preview_req [JobRequestMovePreviewReq] move spec
1121
1337
  # @param [Hash] opts the optional parameters
@@ -1126,7 +1342,7 @@ module Crisphive
1126
1342
  end
1127
1343
 
1128
1344
  # Preview a schedule-board job move
1129
- # Computes (WITHOUT writing) the outcome of moving a confirmed job to a new time and/or technician: where it lands, every later job pushed back per &#x60;mode&#x60;, and the warnings the coordinator would accept (displaced jobs leaving their confirmed windows, overtime). Same technician &#x3D; pure time move; different technician &#x3D; manual reassign. Read-only — safe to call repeatedly while dragging; commit is a separate endpoint. See SCHEDULE_BOARD_DESIGN.md. Warning detail: a TECH_NOT_FEASIBLE warning carries &#x60;reason&#x60; &#x3D; &#x60;cannot_arrive_in_time&#x60; (commute from the tech day-start location / shift start; &#x60;earliest_feasible_at&#x60; (RFC3339 UTC) is the first same-day time they CAN be on site — suggest it as the drop slot) | &#x60;missing_required_skills&#x60; | &#x60;not_available_today&#x60; (no working hours, approved time off, or outside the service area) | &#x60;not_lead_tier&#x60;. For a P0 move this warning is advisory (coordinator may commit anyway); for p1/p2/p3 the same condition is the hard 409 SCHEDULE_MOVE_TECH_INFEASIBLE. 409 NEXT STEPS: SCHEDULE_MOVE_NOT_ELIGIBLE (job unconfirmed/unquoted/archived/completed — not movable) · SCHEDULE_MOVE_IN_PROGRESS (tech already executing — do not move) · SCHEDULE_MOVE_IN_PAST (pick a future time) · SCHEDULE_MOVE_OUTSIDE_WINDOW (landing time outside the customer-confirmed window — hard block; pick a time inside it) · SCHEDULE_MOVE_SLOT_OCCUPIED (landing window blocked by an immovable anchor — another tech/time) · SCHEDULE_MOVE_TECH_INFEASIBLE (non-P0 hard block: target tech not qualified/available — see the TECH_NOT_FEASIBLE warning reasons; change tech or time) · SCHEDULE_MOVE_MULTIDAY_UNSUPPORTED (multi-day jobs not movable v1) · SCHEDULE_MOVE_NO_WORKING_DAY (pick a working day) · SCHEDULE_MOVE_REQUIRES_FREE_SLOT (non-P0 moves may not displace — free capacity only, unless the owner enables allow_non_p0_displacement) · SCHEDULE_MOVE_CREW_UNSTAFFABLE (a crew slot has no feasible replacement at the new time — another time).
1345
+ # Computes (WITHOUT writing) the outcome of moving a confirmed job to a new time and/or technician: where it lands, every later job pushed back per &#x60;mode&#x60;, and the warnings the coordinator would accept (displaced jobs leaving their confirmed windows, overtime). Same technician &#x3D; pure time move; different technician &#x3D; manual reassign. Read-only — safe to call repeatedly while dragging; commit is a separate endpoint. On a day the business does not work, a p0 single-person job previews WITHOUT after_hours_override: the plan is computed as the override would place it and the response carries &#x60;after_hours_override_required: true&#x60; — ask the coordinator whether they have phoned the technician, then commit with after_hours_override&#x3D;true. Any other job is refused with SCHEDULE_MOVE_NO_WORKING_DAY and &#x60;data.override_available: false&#x60;. See SCHEDULE_BOARD_DESIGN.md. Warning detail: a TECH_NOT_FEASIBLE warning carries &#x60;reason&#x60; (&#x3D; &#x60;blockers[0].kind&#x60;) &#x3D; &#x60;outside_service_area&#x60; | &#x60;missing_required_skills&#x60; | &#x60;not_lead_tier&#x60; | &#x60;no_working_day&#x60; | &#x60;on_time_off&#x60; | &#x60;off_shift&#x60; | &#x60;visit_too_long&#x60; | &#x60;cannot_arrive_in_time&#x60; (commute from the tech day-start location / shift start; &#x60;earliest_feasible_at&#x60;, RFC3339 UTC, is the first same-day time they CAN be on site — suggest it as the drop slot) | &#x60;not_available_today&#x60; (diagnosis unavailable). &#x60;blockers[]&#x60; names EVERY hard filter that failed, most-structural first — a client reading only &#x60;reason&#x60; still works. For a P0 move this warning is advisory (coordinator may commit anyway) and carries the SAME &#x60;blockers[]&#x60; a p1/p2/p3 move would get as the hard 409 SCHEDULE_MOVE_TECH_INFEASIBLE below. 409 NEXT STEPS: SCHEDULE_MOVE_NOT_ELIGIBLE (job unconfirmed/unquoted/archived/completed — &#x60;data.failed_precondition&#x60; names which) · SCHEDULE_MOVE_IN_PROGRESS (tech already executing — &#x60;data.fired_actions[]&#x60; lists the actions already fired; do not move) · SCHEDULE_MOVE_IN_PAST (pick a future time — its &#x60;data&#x60; carries &#x60;business_timezone&#x60;, the naive &#x60;start_at&#x60; and the &#x60;start_at_utc&#x60; it resolved to, which is what tells a caller whose own clock says otherwise where the difference came from) · SCHEDULE_MOVE_SLOT_OCCUPIED (landing window blocked by an immovable anchor; &#x60;data.conflicts[]&#x60; names it — another tech/time) · SCHEDULE_MOVE_TECH_INFEASIBLE (non-P0 hard block: target tech not qualified/available — its &#x60;data&#x60; carries &#x60;technician&#x60; (id+name), &#x60;reason&#x60; (same catalog as the TECH_NOT_FEASIBLE warning above), &#x60;blockers[]&#x60; (every cause, most-structural first) and, for &#x60;cannot_arrive_in_time&#x60;, &#x60;earliest_feasible_at&#x60; (RFC3339 UTC) to suggest as the drop slot; change tech or time) · SCHEDULE_MOVE_MULTIDAY_UNSUPPORTED (multi-day jobs not movable v1 — &#x60;data.session_count&#x60;/&#x60;data.session_dates&#x60;) · SCHEDULE_MOVE_NO_WORKING_DAY (&#x60;data.business_timezone&#x60;/&#x60;data.requested_weekday&#x60; — pick a working day, or set after_hours_override&#x3D;true for a P0 whose technician has been phoned) · SCHEDULE_MOVE_REQUIRES_FREE_SLOT (non-P0 moves may not displace — &#x60;data.would_push[]&#x60; names the jobs that would be pushed, &#x60;data.allow_non_p0_displacement: false&#x60; names the setting that would permit it, unless the crew case sets &#x60;data.crew_never_displaces: true&#x60; instead — free capacity only) · SCHEDULE_MOVE_CREW_UNSTAFFABLE (a crew slot has no feasible replacement at the new time — another time). A landing outside the customer-confirmed window is NOT an error — it returns 200 with a MOVED_OUTSIDE_WINDOW warning (customer_window attached) that the coordinator overrides.
1130
1346
  # @param id [String] Job request ID (UUID or short_code)
1131
1347
  # @param job_request_move_preview_req [JobRequestMovePreviewReq] move spec
1132
1348
  # @param [Hash] opts the optional parameters
@@ -1189,22 +1405,22 @@ module Crisphive
1189
1405
  end
1190
1406
 
1191
1407
  # Fire quote (FIXED action — business)
1192
- # Sends the quote: sets quoted_at + duration cols, advances pending_action to confirm_booking. Status stays `booking`.
1408
+ # Sends the quote: sets quoted_at + duration cols, advances pending_action to confirm_booking. Status stays `booking`. job_duration_minutes may be omitted when the job's job type has a default_duration_minutes: the type's default duration and buffers are used (a buffer you send still wins). This is how an automation or voice agent schedules work it cannot size. Before writing, checks that the customer will see at least one slot: the same engine as the customer slot picker runs over the windows the customer asked for with THIS quote's duration (working hours, service areas, time-off, existing bookings, crew coverage). If no slot exists the quote is refused with 409 JOB_REQUEST_QUOTE_NOT_SCHEDULABLE; data.reason says why (outside_working_hours, requested_windows_passed, outside_service_area, off_shift, on_time_off, missing_required_skills, no_technician_available, ...) and data.blocked_by counts the roster per blocker. Send force=true to schedule it anyway after agreeing a time with the customer.
1193
1409
  # @param id [String] Job request ID
1194
1410
  # @param job_request_quote_request [JobRequestQuoteRequest] Quote payload
1195
1411
  # @param [Hash] opts the optional parameters
1196
- # @return [ResponseEnvelope]
1412
+ # @return [QuoteJobRequest200Response]
1197
1413
  def quote_job_request(id, job_request_quote_request, opts = {})
1198
1414
  data, _status_code, _headers = quote_job_request_with_http_info(id, job_request_quote_request, opts)
1199
1415
  data
1200
1416
  end
1201
1417
 
1202
1418
  # Fire quote (FIXED action — business)
1203
- # Sends the quote: sets quoted_at + duration cols, advances pending_action to confirm_booking. Status stays &#x60;booking&#x60;.
1419
+ # Sends the quote: sets quoted_at + duration cols, advances pending_action to confirm_booking. Status stays &#x60;booking&#x60;. job_duration_minutes may be omitted when the job&#39;s job type has a default_duration_minutes: the type&#39;s default duration and buffers are used (a buffer you send still wins). This is how an automation or voice agent schedules work it cannot size. Before writing, checks that the customer will see at least one slot: the same engine as the customer slot picker runs over the windows the customer asked for with THIS quote&#39;s duration (working hours, service areas, time-off, existing bookings, crew coverage). If no slot exists the quote is refused with 409 JOB_REQUEST_QUOTE_NOT_SCHEDULABLE; data.reason says why (outside_working_hours, requested_windows_passed, outside_service_area, off_shift, on_time_off, missing_required_skills, no_technician_available, ...) and data.blocked_by counts the roster per blocker. Send force&#x3D;true to schedule it anyway after agreeing a time with the customer.
1204
1420
  # @param id [String] Job request ID
1205
1421
  # @param job_request_quote_request [JobRequestQuoteRequest] Quote payload
1206
1422
  # @param [Hash] opts the optional parameters
1207
- # @return [Array<(ResponseEnvelope, Integer, Hash)>] ResponseEnvelope data, response status code and response headers
1423
+ # @return [Array<(QuoteJobRequest200Response, Integer, Hash)>] QuoteJobRequest200Response data, response status code and response headers
1208
1424
  def quote_job_request_with_http_info(id, job_request_quote_request, opts = {})
1209
1425
  if @api_client.config.debugging
1210
1426
  @api_client.config.logger.debug 'Calling API: JobRequestBusinessApi.quote_job_request ...'
@@ -1240,7 +1456,7 @@ module Crisphive
1240
1456
  post_body = opts[:debug_body] || @api_client.object_to_http_body(job_request_quote_request)
1241
1457
 
1242
1458
  # return_type
1243
- return_type = opts[:debug_return_type] || 'ResponseEnvelope'
1459
+ return_type = opts[:debug_return_type] || 'QuoteJobRequest200Response'
1244
1460
 
1245
1461
  # auth_names
1246
1462
  auth_names = opts[:debug_auth_names] || ['ApiKeyAuth']
@@ -1263,7 +1479,7 @@ module Crisphive
1263
1479
  end
1264
1480
 
1265
1481
  # Set job priority (scheduling staff)
1266
- # Sets the P0–P3 priority on a non-archived, non-completed job (Owner / Administrator / Booking Coordinator). Allowed values: \"p0\" (emergency, interrupt-driven) | \"p1\" (top — displaced only by p0; may carry an sla_deadline arming auto-escalation) | \"p2\" (standard) | \"p3\" (deferrable, first displacement victim). sla_deadline is only valid with p1 and must be in the future (business-local naive datetime); moving away from p1 disarms the SLA clock. Accepts UUID or short_code in :id.
1482
+ # Sets the P0–P3 priority on a non-archived, non-completed job (Owner / Administrator / Booking Coordinator). Allowed values: \"p0\" (emergency, interrupt-driven) | \"p1\" (top — displaced only by p0; may carry an sla_deadline arming auto-escalation) | \"p2\" (standard) | \"p3\" (deferrable, first displacement victim). sla_deadline is only valid with p1 and must be in the future (business-local naive datetime); moving away from p1 disarms the SLA clock. Accepts UUID or short_code in :id. Lowering a job to anything but p0 never moves it: when it still sits outside the business's working hours (typically placed there earlier through the after-hours override) the response carries an AFTER_HOURS warning per affected session, and the job should be moved back to a working day.
1267
1483
  # @param id [String] Job request ID or short_code
1268
1484
  # @param job_request_update_priority_request [JobRequestUpdatePriorityRequest] Priority payload
1269
1485
  # @param [Hash] opts the optional parameters
@@ -1274,7 +1490,7 @@ module Crisphive
1274
1490
  end
1275
1491
 
1276
1492
  # Set job priority (scheduling staff)
1277
- # Sets the P0–P3 priority on a non-archived, non-completed job (Owner / Administrator / Booking Coordinator). Allowed values: \&quot;p0\&quot; (emergency, interrupt-driven) | \&quot;p1\&quot; (top — displaced only by p0; may carry an sla_deadline arming auto-escalation) | \&quot;p2\&quot; (standard) | \&quot;p3\&quot; (deferrable, first displacement victim). sla_deadline is only valid with p1 and must be in the future (business-local naive datetime); moving away from p1 disarms the SLA clock. Accepts UUID or short_code in :id.
1493
+ # Sets the P0–P3 priority on a non-archived, non-completed job (Owner / Administrator / Booking Coordinator). Allowed values: \&quot;p0\&quot; (emergency, interrupt-driven) | \&quot;p1\&quot; (top — displaced only by p0; may carry an sla_deadline arming auto-escalation) | \&quot;p2\&quot; (standard) | \&quot;p3\&quot; (deferrable, first displacement victim). sla_deadline is only valid with p1 and must be in the future (business-local naive datetime); moving away from p1 disarms the SLA clock. Accepts UUID or short_code in :id. Lowering a job to anything but p0 never moves it: when it still sits outside the business&#39;s working hours (typically placed there earlier through the after-hours override) the response carries an AFTER_HOURS warning per affected session, and the job should be moved back to a working day.
1278
1494
  # @param id [String] Job request ID or short_code
1279
1495
  # @param job_request_update_priority_request [JobRequestUpdatePriorityRequest] Priority payload
1280
1496
  # @param [Hash] opts the optional parameters