call_attempt.rb

  1# frozen_string_literal: true
  2
  3require "value_semantics/monkey_patched"
  4
  5require_relative "tts_template"
  6require_relative "low_balance"
  7
  8class CallAttempt
  9	def self.for(customer, rate, usage, trust_level, direction:, **kwargs)
 10		kwargs.merge!(direction: direction)
 11		credit = [customer.minute_limit.to_d - usage, 0].max + customer.balance
 12		if !rate || !trust_level.support_call?(rate)
 13			Unsupported.new(direction: direction)
 14		elsif credit < rate * 10
 15			NoBalance.for(customer, rate, usage, trust_level, **kwargs)
 16		else
 17			for_ask_or_go(customer, rate, usage, credit, **kwargs)
 18		end
 19	end
 20
 21	def self.for_ask_or_go(customer, rate, usage, credit, digits: nil, **kwargs)
 22		kwargs.merge!(
 23			customer_id: customer.customer_id,
 24			limit_remaining: limit_remaining(customer, usage, rate),
 25			max_minutes: (credit / rate).to_i
 26		)
 27		if digits != "1" && limit_remaining(customer, usage, rate) < 10
 28			AtLimit.new(**kwargs)
 29		else
 30			new(**kwargs)
 31		end
 32	end
 33
 34	def self.limit_remaining(customer, usage, rate)
 35		can_use = customer.minute_limit.to_d + customer.monthly_overage_limit
 36		([can_use - usage, 0].max / rate).to_i
 37	end
 38
 39	value_semantics do
 40		customer_id String
 41		from String
 42		to(/\A\+\d+\Z/)
 43		call_id String
 44		direction Either(:inbound, :outbound)
 45		limit_remaining Integer
 46		max_minutes Integer
 47	end
 48
 49	def to_render
 50		["#{direction}/connect", { locals: to_h }]
 51	end
 52
 53	def create_call(fwd, *args, &block)
 54		fwd.create_call(*args, &block)
 55	end
 56
 57	def as_json(*)
 58		{
 59			from: from,
 60			to: to,
 61			customer_id: customer_id,
 62			limit_remaining: limit_remaining,
 63			max_minutes: max_minutes
 64		}
 65	end
 66
 67	def to_json(*args)
 68		as_json.to_json(*args)
 69	end
 70
 71	class Unsupported
 72		value_semantics do
 73			direction Either(:inbound, :outbound)
 74		end
 75
 76		def view
 77			"#{direction}/unsupported"
 78		end
 79
 80		def tts
 81			TTSTemplate.new(view).tts(self)
 82		end
 83
 84		def to_render
 85			[view]
 86		end
 87
 88		def create_call(*); end
 89
 90		def as_json(*)
 91			{ tts: tts }
 92		end
 93
 94		def to_json(*args)
 95			as_json.to_json(*args)
 96		end
 97	end
 98
 99	class NoBalance
100		def self.for(customer, rate, usage, trust_level, direction:, **kwargs)
101			LowBalance.for(customer).then(&:notify!).then do |amount|
102				if amount&.positive?
103					CallAttempt.for(
104						customer.with_balance(customer.balance + amount),
105						rate, usage, trust_level, direction: direction, **kwargs
106					)
107				else
108					NoBalance.new(balance: customer.balance, direction: direction)
109				end
110			end
111		end
112
113		value_semantics do
114			balance Numeric
115			direction Either(:inbound, :outbound)
116		end
117
118		def view
119			"#{direction}/no_balance"
120		end
121
122		def tts
123			TTSTemplate.new(view).tts(self)
124		end
125
126		def to_render
127			[view, { locals: to_h }]
128		end
129
130		def create_call(*); end
131
132		def as_json(*)
133			{ tts: tts }
134		end
135
136		def to_json(*args)
137			as_json.to_json(*args)
138		end
139	end
140
141	class AtLimit
142		value_semantics do
143			customer_id String
144			from String
145			to(/\A\+\d+\Z/)
146			call_id String
147			direction Either(:inbound, :outbound)
148			limit_remaining Integer
149			max_minutes Integer
150		end
151
152		def view
153			"#{direction}/at_limit"
154		end
155
156		def tts
157			TTSTemplate.new(view).tts(self)
158		end
159
160		def to_render
161			[view, { locals: to_h }]
162		end
163
164		def create_call(fwd, *args, &block)
165			fwd.create_call(*args, &block)
166		end
167
168		def as_json(*)
169			{
170				tts: tts,
171				from: from,
172				to: to,
173				customer_id: customer_id,
174				limit_remaining: limit_remaining,
175				max_minutes: max_minutes
176			}
177		end
178
179		def to_json(*args)
180			as_json.to_json(*args)
181		end
182	end
183end