Class: Attio::Deal
- Inherits:
-
TypedRecord
- Object
- APIResource
- Internal::Record
- TypedRecord
- Attio::Deal
- Defined in:
- lib/attio/resources/deal.rb
Overview
Represents a Deal record in Attio
Constant Summary
Constants inherited from APIResource
Instance Attribute Summary
Attributes inherited from Internal::Record
#attio_object_id, #object_api_slug
Attributes inherited from APIResource
Class Method Summary collapse
-
.closed_in_period(period, **opts) ⇒ Array<Attio::Deal>
Get deals that closed in a specific time period.
-
.closed_in_quarter(year, quarter, **opts) ⇒ Array<Attio::Deal>
Get deals that closed in a specific quarter.
-
.create(name:, value: nil, stage: nil, status: nil, owner: nil, associated_people: nil, associated_company: nil, values: {}, **opts) ⇒ Object
Create a deal with a simplified interface.
-
.created_in_period(period, **opts) ⇒ Array<Attio::Deal>
Get deals created in a specific period.
-
.current_quarter_metrics(**opts) ⇒ Hash
Get current quarter metrics.
-
.find_by_owner(owner_id, **opts) ⇒ Attio::ListObject
Find deals by owner.
-
.find_by_value_range(min: nil, max: nil, **opts) ⇒ Attio::ListObject
Find deals within a value range.
-
.high_value(threshold = 50_000, **opts) ⇒ Array<Attio::Deal>
Get high-value deals above a threshold.
-
.in_stage(stage_names:, **opts) ⇒ Attio::ListObject
Find deals by stage names.
-
.last_30_days_metrics(**opts) ⇒ Hash
Get last 30 days metrics.
-
.lost(**opts) ⇒ Attio::ListObject
Find lost deals using configured statuses.
-
.metrics_for_period(period, **opts) ⇒ Hash
Get metrics for any time period.
-
.month_to_date_metrics(**opts) ⇒ Hash
Get month-to-date metrics.
-
.open_deals(**opts) ⇒ Attio::ListObject
Find open deals (Lead + In Progress) using configured statuses.
-
.recently_created(days = 7, **opts) ⇒ Array<Attio::Deal>
Get recently created deals.
-
.unassigned(**opts) ⇒ Array<Attio::Deal>
Get deals without owners.
-
.won(**opts) ⇒ Attio::ListObject
Find won deals using configured statuses.
-
.year_to_date_metrics(**opts) ⇒ Hash
Get year-to-date metrics.
Instance Method Summary collapse
-
#amount ⇒ Float
Get the monetary amount from the deal value.
-
#closed? ⇒ Boolean
Check if the deal is closed (won or lost).
-
#closed_at ⇒ Time?
Get the timestamp when the deal was closed (won or lost).
-
#company ⇒ Hash?
Get the company reference.
-
#company_record(**opts) ⇒ Attio::Company?
Get the associated company record.
-
#currency ⇒ String
Get the currency code.
-
#current_status ⇒ String?
Get the current status title (delegates to stage for simplicity).
-
#days_in_stage ⇒ Integer
Get the number of days the deal has been in current stage.
-
#enterprise? ⇒ Boolean
Check if this is an enterprise deal.
-
#formatted_amount ⇒ String
Get formatted amount for display.
-
#lost? ⇒ Boolean
Check if the deal is lost.
-
#mid_market? ⇒ Boolean
Check if this is a mid-market deal.
-
#name ⇒ String?
Get the deal name.
-
#needs_attention?(stale_days = 30) ⇒ Boolean
Check if deal needs attention (stale and not closed).
-
#open? ⇒ Boolean
Check if the deal is open.
-
#owner ⇒ Hash?
Get the owner reference.
-
#owner_record(**opts) ⇒ Attio::WorkspaceMember?
Get the owner workspace member record.
-
#raw_value ⇒ Object
Get the raw value data from the API.
-
#size_category ⇒ Symbol
Get deal size category.
-
#small? ⇒ Boolean
Check if this is a small deal.
-
#stage ⇒ String?
(also: #status)
Get the normalized deal stage/status.
-
#stale?(days = 30) ⇒ Boolean
Check if the deal is stale (no activity for specified days).
-
#status_changed_at ⇒ Time?
Get the timestamp when the status changed.
-
#summary ⇒ String
Get a simple summary of the deal.
-
#to_s ⇒ String
Convert to string for display.
-
#update_stage(new_stage, **opts) ⇒ Attio::Deal
Update the deal stage.
-
#update_status(new_status, **opts) ⇒ Attio::Deal
Alias for update_stage (for compatibility).
-
#update_value(new_value, **opts) ⇒ Attio::Deal
Update the deal value.
-
#value ⇒ Object
deprecated
Deprecated.
Use #amount for monetary values or #raw_value for raw API response
-
#velocity ⇒ Float?
Get deal velocity (amount per day if closed).
-
#won? ⇒ Boolean
Check if the deal is won.
-
#won_at ⇒ Time?
Get the timestamp when the deal was won.
Methods inherited from TypedRecord
all, delete, #destroy, find, find_by, #initialize, list, object_type, retrieve, #save, search, update
Methods inherited from Internal::Record
#add_to_list, #destroy, #initialize, #inspect, list, #lists, resource_path, #resource_path, retrieve, #save, search, #to_h, update
Methods inherited from APIResource
#==, #[], #[]=, api_operations, attr_attio, #changed, #changed?, #changed_attributes, #changes, #destroy, #each, execute_request, #fetch, #hash, id_param_name, #initialize, #inspect, #key?, #keys, #persisted?, prepare_params_for_create, prepare_params_for_update, #reset_changes!, resource_name, resource_path, #resource_path, #revert!, #save, #to_h, #to_json, #update_attributes, #update_from, validate_id!, #values
Constructor Details
This class inherits a constructor from Attio::TypedRecord
Class Method Details
.closed_in_period(period, **opts) ⇒ Array<Attio::Deal>
Get deals that closed in a specific time period
177 178 179 180 181 182 |
# File 'lib/attio/resources/deal.rb', line 177 def closed_in_period(period, **opts) all(**opts).select do |deal| closed_date = deal.closed_at closed_date && period.includes?(closed_date) end end |
.closed_in_quarter(year, quarter, **opts) ⇒ Array<Attio::Deal>
Get deals that closed in a specific quarter
188 189 190 191 |
# File 'lib/attio/resources/deal.rb', line 188 def closed_in_quarter(year, quarter, **opts) period = Util::TimePeriod.quarter(year, quarter) closed_in_period(period, **opts) end |
.create(name:, value: nil, stage: nil, status: nil, owner: nil, associated_people: nil, associated_company: nil, values: {}, **opts) ⇒ Object
Create a deal with a simplified interface
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/attio/resources/deal.rb', line 41 def create(name:, value: nil, stage: nil, status: nil, owner: nil, associated_people: nil, associated_company: nil, values: {}, **opts) # Name is required and simple values[:name] = name if name && !values[:name] # Add optional fields values[:value] = value if value && !values[:value] # Handle stage vs status - API uses "stage" but we support both if (stage || status) && !values[:stage] values[:stage] = stage || status end # Handle owner - can be email address or workspace member reference if owner && !values[:owner] values[:owner] = owner end # Handle associated people - convert email array to proper format if associated_people && !values[:associated_people] values[:associated_people] = associated_people.map do |email| { target_object: "people", email_addresses: [ {email_address: email} ] } end end # Handle associated company - convert domain array to proper format if associated_company && !values[:associated_company] # associated_company can be array of domains or single domain domains = associated_company.is_a?(Array) ? associated_company : [associated_company] values[:associated_company] = { target_object: "companies", domains: domains.map { |domain| {domain: domain} } } end super(values: values, **opts) end |
.created_in_period(period, **opts) ⇒ Array<Attio::Deal>
Get deals created in a specific period
293 294 295 296 297 298 |
# File 'lib/attio/resources/deal.rb', line 293 def created_in_period(period, **opts) all(**opts).select do |deal| created_at = deal.created_at created_at && period.includes?(created_at) end end |
.current_quarter_metrics(**opts) ⇒ Hash
Get current quarter metrics
248 249 250 |
# File 'lib/attio/resources/deal.rb', line 248 def current_quarter_metrics(**opts) metrics_for_period(Util::TimePeriod.current_quarter, **opts) end |
.find_by_owner(owner_id, **opts) ⇒ Attio::ListObject
Find deals by owner
163 164 165 166 167 168 169 170 171 172 |
# File 'lib/attio/resources/deal.rb', line 163 def find_by_owner(owner_id, **opts) list(**opts.merge(params: { filter: { owner: { target_object: "workspace_members", target_record_id: owner_id } } })) end |
.find_by_value_range(min: nil, max: nil, **opts) ⇒ Attio::ListObject
Find deals within a value range
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/attio/resources/deal.rb', line 124 def find_by_value_range(min: nil, max: nil, **opts) filters = [] filters << {value: {"$gte": min}} if min filters << {value: {"$lte": max}} if max filter = if filters.length == 1 filters.first elsif filters.length > 1 {"$and": filters} else {} end list(**opts.merge(params: {filter: filter})) end |
.high_value(threshold = 50_000, **opts) ⇒ Array<Attio::Deal>
Get high-value deals above a threshold
273 274 275 |
# File 'lib/attio/resources/deal.rb', line 273 def high_value(threshold = 50_000, **opts) all(**opts).select { |deal| deal.amount > threshold } end |
.in_stage(stage_names:, **opts) ⇒ Attio::ListObject
Find deals by stage names
87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/attio/resources/deal.rb', line 87 def in_stage(stage_names:, **opts) # If only one stage, use simple equality filter = if stage_names.length == 1 {stage: stage_names.first} else # Multiple stages need $or operator { "$or": stage_names.map { |stage| {stage: stage} } } end list(**opts.merge(params: {filter: filter})) end |
.last_30_days_metrics(**opts) ⇒ Hash
Get last 30 days metrics
266 267 268 |
# File 'lib/attio/resources/deal.rb', line 266 def last_30_days_metrics(**opts) metrics_for_period(Util::TimePeriod.last_30_days, **opts) end |
.lost(**opts) ⇒ Attio::ListObject
Find lost deals using configured statuses
109 110 111 |
# File 'lib/attio/resources/deal.rb', line 109 def lost(**opts) in_stage(stage_names: Attio.configuration.lost_statuses, **opts) end |
.metrics_for_period(period, **opts) ⇒ Hash
Get metrics for any time period
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 |
# File 'lib/attio/resources/deal.rb', line 196 def metrics_for_period(period, **opts) # Build date filter for stage.active_from # Note: We need to add a day to end_date to include all of that day # since stage.active_from includes time date_filter = { "stage" => { "active_from" => { "$gte" => period.start_date.strftime("%Y-%m-%d"), "$lte" => (period.end_date + 1).strftime("%Y-%m-%d") } } } # Fetch won deals closed in the period won_statuses = ::Attio.configuration.won_statuses won_conditions = won_statuses.map { |status| {"stage" => status} } won_filter = { "$and" => [ ((won_conditions.size > 1) ? {"$or" => won_conditions} : won_conditions.first), date_filter ].compact } won_response = list(**opts.merge(params: {filter: won_filter})) # Fetch lost deals closed in the period lost_statuses = ::Attio.configuration.lost_statuses lost_conditions = lost_statuses.map { |status| {"stage" => status} } lost_filter = { "$and" => [ ((lost_conditions.size > 1) ? {"$or" => lost_conditions} : lost_conditions.first), date_filter ].compact } lost_response = list(**opts.merge(params: {filter: lost_filter})) won_deals = won_response.data lost_deals = lost_response.data total_closed = won_deals.size + lost_deals.size { period: period.label, won_count: won_deals.size, won_amount: won_deals.sum(&:amount), lost_count: lost_deals.size, lost_amount: lost_deals.sum(&:amount), total_closed: total_closed, win_rate: (total_closed > 0) ? (won_deals.size.to_f / total_closed * 100).round(2) : 0.0 } end |
.month_to_date_metrics(**opts) ⇒ Hash
Get month-to-date metrics
260 261 262 |
# File 'lib/attio/resources/deal.rb', line 260 def month_to_date_metrics(**opts) metrics_for_period(Util::TimePeriod.month_to_date, **opts) end |
.open_deals(**opts) ⇒ Attio::ListObject
Find open deals (Lead + In Progress) using configured statuses
115 116 117 118 |
# File 'lib/attio/resources/deal.rb', line 115 def open_deals(**opts) all_open_statuses = Attio.configuration.open_statuses + Attio.configuration.in_progress_statuses in_stage(stage_names: all_open_statuses, **opts) end |
.recently_created(days = 7, **opts) ⇒ Array<Attio::Deal>
Get recently created deals
286 287 288 |
# File 'lib/attio/resources/deal.rb', line 286 def recently_created(days = 7, **opts) created_in_period(Util::TimePeriod.last_days(days), **opts) end |
.unassigned(**opts) ⇒ Array<Attio::Deal>
Get deals without owners
279 280 281 |
# File 'lib/attio/resources/deal.rb', line 279 def unassigned(**opts) all(**opts).select { |deal| deal.owner.nil? } end |
.won(**opts) ⇒ Attio::ListObject
Find won deals using configured statuses
103 104 105 |
# File 'lib/attio/resources/deal.rb', line 103 def won(**opts) in_stage(stage_names: Attio.configuration.won_statuses, **opts) end |
.year_to_date_metrics(**opts) ⇒ Hash
Get year-to-date metrics
254 255 256 |
# File 'lib/attio/resources/deal.rb', line 254 def year_to_date_metrics(**opts) metrics_for_period(Util::TimePeriod.year_to_date, **opts) end |
Instance Method Details
#amount ⇒ Float
Get the monetary amount from the deal value
316 317 318 319 |
# File 'lib/attio/resources/deal.rb', line 316 def amount return 0.0 unless self[:value].is_a?(Hash) (self[:value]["currency_value"] || 0).to_f end |
#closed? ⇒ Boolean
Check if the deal is closed (won or lost)
540 541 542 |
# File 'lib/attio/resources/deal.rb', line 540 def closed? won? || lost? end |
#closed_at ⇒ Time?
Get the timestamp when the deal was closed (won or lost)
493 494 495 496 |
# File 'lib/attio/resources/deal.rb', line 493 def closed_at return nil unless won? || lost? status_changed_at end |
#company ⇒ Hash?
Get the company reference
382 383 384 |
# File 'lib/attio/resources/deal.rb', line 382 def company self[:company] end |
#company_record(**opts) ⇒ Attio::Company?
Get the associated company record
416 417 418 419 420 421 |
# File 'lib/attio/resources/deal.rb', line 416 def company_record(**opts) return nil unless company company_id = company.is_a?(Hash) ? company["target_record_id"] : company Company.retrieve(company_id, **opts) if company_id end |
#currency ⇒ String
Get the currency code
323 324 325 326 |
# File 'lib/attio/resources/deal.rb', line 323 def currency return "USD" unless self[:value].is_a?(Hash) self[:value]["currency_code"] || "USD" end |
#current_status ⇒ String?
Get the current status title (delegates to stage for simplicity)
445 446 447 |
# File 'lib/attio/resources/deal.rb', line 445 def current_status stage end |
#days_in_stage ⇒ Integer
Get the number of days the deal has been in current stage
525 526 527 528 |
# File 'lib/attio/resources/deal.rb', line 525 def days_in_stage return 0 unless status_changed_at ((Time.now - status_changed_at) / (24 * 60 * 60)).round end |
#enterprise? ⇒ Boolean
Check if this is an enterprise deal
507 508 509 |
# File 'lib/attio/resources/deal.rb', line 507 def enterprise? amount > 100_000 end |
#formatted_amount ⇒ String
Get formatted amount for display
330 331 332 |
# File 'lib/attio/resources/deal.rb', line 330 def formatted_amount Util::CurrencyFormatter.format(amount, currency) end |
#lost? ⇒ Boolean
Check if the deal is lost
478 479 480 481 482 |
# File 'lib/attio/resources/deal.rb', line 478 def lost? return false unless current_status Attio.configuration.lost_statuses.include?(current_status) end |
#mid_market? ⇒ Boolean
Check if this is a mid-market deal
513 514 515 |
# File 'lib/attio/resources/deal.rb', line 513 def mid_market? amount.between?(10_000, 100_000) end |
#name ⇒ String?
Get the deal name
310 311 312 |
# File 'lib/attio/resources/deal.rb', line 310 def name self[:name] end |
#needs_attention?(stale_days = 30) ⇒ Boolean
Check if deal needs attention (stale and not closed)
571 572 573 |
# File 'lib/attio/resources/deal.rb', line 571 def needs_attention?(stale_days = 30) !closed? && stale?(stale_days) end |
#open? ⇒ Boolean
Check if the deal is open
461 462 463 464 465 466 |
# File 'lib/attio/resources/deal.rb', line 461 def open? return false unless current_status all_open_statuses = Attio.configuration.open_statuses + Attio.configuration.in_progress_statuses all_open_statuses.include?(current_status) end |
#owner ⇒ Hash?
Get the owner reference
376 377 378 |
# File 'lib/attio/resources/deal.rb', line 376 def owner self[:owner] end |
#owner_record(**opts) ⇒ Attio::WorkspaceMember?
Get the owner workspace member record
425 426 427 428 429 430 431 432 433 434 |
# File 'lib/attio/resources/deal.rb', line 425 def owner_record(**opts) return nil unless owner owner_id = if owner.is_a?(Hash) owner["referenced_actor_id"] || owner["target_record_id"] else owner end WorkspaceMember.retrieve(owner_id, **opts) if owner_id end |
#raw_value ⇒ Object
Get the raw value data from the API
344 345 346 |
# File 'lib/attio/resources/deal.rb', line 344 def raw_value self[:value] end |
#size_category ⇒ Symbol
Get deal size category
558 559 560 561 562 563 564 565 566 |
# File 'lib/attio/resources/deal.rb', line 558 def size_category if enterprise? :enterprise elsif mid_market? :mid_market else :small end end |
#small? ⇒ Boolean
Check if this is a small deal
519 520 521 |
# File 'lib/attio/resources/deal.rb', line 519 def small? amount < 10_000 end |
#stage ⇒ String? Also known as: status
Get the normalized deal stage/status
350 351 352 353 354 355 356 |
# File 'lib/attio/resources/deal.rb', line 350 def stage stage_data = self[:stage] return nil unless stage_data.is_a?(Hash) # Attio always returns stage as a hash with nested status.title stage_data.dig("status", "title") end |
#stale?(days = 30) ⇒ Boolean
Check if the deal is stale (no activity for specified days)
533 534 535 536 |
# File 'lib/attio/resources/deal.rb', line 533 def stale?(days = 30) return false if closed? days_in_stage > days end |
#status_changed_at ⇒ Time?
Get the timestamp when the status changed
451 452 453 454 455 456 457 |
# File 'lib/attio/resources/deal.rb', line 451 def status_changed_at return nil unless self[:stage].is_a?(Hash) # Attio returns active_from at the top level of the stage hash = self[:stage]["active_from"] ? Time.parse() : nil end |
#summary ⇒ String
Get a simple summary of the deal
546 547 548 |
# File 'lib/attio/resources/deal.rb', line 546 def summary "#{name || "Unnamed Deal"}: #{formatted_amount} (#{stage || "No Stage"})" end |
#to_s ⇒ String
Convert to string for display
552 553 554 |
# File 'lib/attio/resources/deal.rb', line 552 def to_s summary end |
#update_stage(new_stage, **opts) ⇒ Attio::Deal
Update the deal stage
389 390 391 |
# File 'lib/attio/resources/deal.rb', line 389 def update_stage(new_stage, **opts) self.class.update(id, values: {stage: new_stage}, **opts) end |
#update_status(new_status, **opts) ⇒ Attio::Deal
Alias for update_stage (for compatibility)
396 397 398 |
# File 'lib/attio/resources/deal.rb', line 396 def update_status(new_status, **opts) update_stage(new_status, **opts) end |
#update_value(new_value, **opts) ⇒ Attio::Deal
Update the deal value
410 411 412 |
# File 'lib/attio/resources/deal.rb', line 410 def update_value(new_value, **opts) self.class.update(id, values: {value: new_value}, **opts) end |
#value ⇒ Object
Use #amount for monetary values or #raw_value for raw API response
Get the raw deal value (for backward compatibility)
337 338 339 340 |
# File 'lib/attio/resources/deal.rb', line 337 def value warn "[DEPRECATION] `value` is deprecated. Use `amount` for monetary values or `raw_value` for the raw API response." unless ENV["ATTIO_SUPPRESS_DEPRECATION"] amount end |
#velocity ⇒ Float?
Get deal velocity (amount per day if closed)
577 578 579 580 581 582 |
# File 'lib/attio/resources/deal.rb', line 577 def velocity return nil unless closed? && closed_at && created_at days_to_close = ((closed_at - created_at) / (24 * 60 * 60)).round (days_to_close > 0) ? (amount / days_to_close).round(2) : amount end |
#won? ⇒ Boolean
Check if the deal is won
470 471 472 473 474 |
# File 'lib/attio/resources/deal.rb', line 470 def won? return false unless current_status Attio.configuration.won_statuses.include?(current_status) end |
#won_at ⇒ Time?
Get the timestamp when the deal was won
486 487 488 489 |
# File 'lib/attio/resources/deal.rb', line 486 def won_at return nil unless won? status_changed_at end |