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:, usage:, **kwargs)
 10		credit = [customer.minute_limit.to_d - usage, 0].max + customer.balance
 11		@kinds.each do |kind|
 12			ca = kind.call(
 13				customer: customer, usage: usage, credit: credit,
 14				**kwargs.merge(limits(customer, usage, credit, **kwargs))
 15			)
 16			return ca if ca
 17		end
 18
 19		raise "No CallAttempt matched"
 20	end
 21
 22	def self.limits(customer, usage, credit, rate:, **)
 23		return {} unless customer && usage && rate
 24
 25		can_use = customer.minute_limit.to_d + customer.monthly_overage_limit
 26		{
 27			limit_remaining: ([can_use - usage, 0].max / rate).to_i,
 28			max_minutes: (credit / rate).to_i
 29		}
 30	end
 31
 32	def self.register(&maybe_mk)
 33		@kinds ||= []
 34		@kinds << maybe_mk
 35	end
 36
 37	value_semantics do
 38		customer_id String
 39		from String
 40		to(/\A\+\d+\Z/)
 41		call_id String
 42		direction Either(:inbound, :outbound)
 43		limit_remaining Integer
 44		max_minutes Integer
 45	end
 46
 47	def to_render
 48		["#{direction}/connect", { locals: to_h }]
 49	end
 50
 51	def create_call(fwd, *args, &block)
 52		fwd.create_call(*args, &block)
 53	end
 54
 55	def as_json(*)
 56		{
 57			from: from,
 58			to: to,
 59			customer_id: customer_id,
 60			limit_remaining: limit_remaining,
 61			max_minutes: max_minutes
 62		}
 63	end
 64
 65	def to_json(*args)
 66		as_json.to_json(*args)
 67	end
 68
 69	class Expired
 70		CallAttempt.register do |customer:, direction:, **|
 71			new(direction: direction) if customer.plan_name && !customer.active?
 72		end
 73
 74		value_semantics do
 75			direction Either(:inbound, :outbound)
 76		end
 77
 78		def view
 79			"#{direction}/expired"
 80		end
 81
 82		def tts
 83			TTSTemplate.new(view).tts(self)
 84		end
 85
 86		def to_render
 87			[view]
 88		end
 89
 90		def create_call(*); end
 91
 92		def as_json(*)
 93			{ tts: tts }
 94		end
 95
 96		def to_json(*args)
 97			as_json.to_json(*args)
 98		end
 99	end
100
101	class Unsupported
102		CallAttempt.register do |supported:, direction:, **|
103			new(direction: direction) unless supported
104		end
105
106		value_semantics do
107			direction Either(:inbound, :outbound)
108		end
109
110		def view
111			"#{direction}/unsupported"
112		end
113
114		def tts
115			TTSTemplate.new(view).tts(self)
116		end
117
118		def to_render
119			[view]
120		end
121
122		def create_call(*); end
123
124		def as_json(*)
125			{ tts: tts }
126		end
127
128		def to_json(*args)
129			as_json.to_json(*args)
130		end
131	end
132
133	class NoBalance
134		CallAttempt.register do |credit:, rate:, **kwargs|
135			self.for(rate: rate, **kwargs) if credit < rate * 10
136		end
137
138		def self.for(customer:, direction:, **kwargs)
139			LowBalance.for(customer).then(&:notify!).then do |amount|
140				if amount&.positive?
141					CallAttempt.for(
142						customer: customer.with_balance(customer.balance + amount),
143						**kwargs.merge(direction: direction)
144					)
145				else
146					NoBalance.new(balance: customer.balance, direction: direction)
147				end
148			end
149		end
150
151		value_semantics do
152			balance Numeric
153			direction Either(:inbound, :outbound)
154		end
155
156		def view
157			"#{direction}/no_balance"
158		end
159
160		def tts
161			TTSTemplate.new(view).tts(self)
162		end
163
164		def to_render
165			[view, { locals: to_h }]
166		end
167
168		def create_call(*); end
169
170		def as_json(*)
171			{ tts: tts }
172		end
173
174		def to_json(*args)
175			as_json.to_json(*args)
176		end
177	end
178
179	class AtLimit
180		value_semantics do
181			customer_id String
182			from String
183			to(/\A\+\d+\Z/)
184			call_id String
185			direction Either(:inbound, :outbound)
186			limit_remaining Integer
187			max_minutes Integer
188		end
189
190		CallAttempt.register do |digits: nil, limit_remaining:, customer:, **kwargs|
191			if digits != "1" && limit_remaining < 10
192				new(
193					**kwargs
194						.merge(
195							limit_remaining: limit_remaining,
196							customer_id: customer.customer_id
197						).slice(*value_semantics.attributes.map(&:name))
198				)
199			end
200		end
201
202		def view
203			"#{direction}/at_limit"
204		end
205
206		def tts
207			TTSTemplate.new(view).tts(self)
208		end
209
210		def to_render
211			[view, { locals: to_h }]
212		end
213
214		def create_call(fwd, *args, &block)
215			fwd.create_call(*args, &block)
216		end
217
218		def as_json(*)
219			{
220				tts: tts,
221				from: from,
222				to: to,
223				customer_id: customer_id,
224				limit_remaining: limit_remaining,
225				max_minutes: max_minutes
226			}
227		end
228
229		def to_json(*args)
230			as_json.to_json(*args)
231		end
232	end
233
234	register do |customer:, **kwargs|
235		new(
236			**kwargs
237				.merge(customer_id: customer.customer_id)
238				.slice(*value_semantics.attributes.map(&:name))
239		)
240	end
241end