1# frozen_string_literal: true
2
3require "bigdecimal"
4require "forwardable"
5require "relative_time"
6require "value_semantics/monkey_patched"
7
8require_relative "api"
9require_relative "call_attempt_repo"
10require_relative "customer"
11require_relative "customer_plan"
12require_relative "form_template"
13require_relative "promise_hash"
14require_relative "proxied_jid"
15
16class PlanInfo
17 extend Forwardable
18
19 def_delegators :plan, :expires_at, :auto_top_up_amount
20
21 def self.for(plan_or_customer)
22 return EMPromise.resolve(NoPlan.new) unless plan_or_customer&.plan_name
23
24 plan_or_customer.activation_date.then do |adate|
25 new(plan: plan_or_customer, start_date: adate)
26 end
27 end
28
29 class NoPlan
30 def template
31 FormTemplate.new("")
32 end
33
34 def admin_template
35 FormTemplate.new("")
36 end
37
38 def status
39 "Transitional"
40 end
41 end
42
43 value_semantics do
44 plan Either(CustomerPlan, Customer)
45 start_date Time
46 end
47
48 def template
49 FormTemplate.for("plan_info", plan_info: self)
50 end
51
52 def admin_template
53 FormTemplate.for("admin_plan_info", plan_info: self)
54 end
55
56 def monthly_price
57 "$%.4f / month" % plan.monthly_price
58 end
59
60 def relative_start_date
61 RelativeTime.in_words(start_date)
62 end
63
64 def formatted_start_date
65 start_date.strftime("%Y-%m-%d %H:%M:%S")
66 end
67
68 def currency
69 (plan.currency || "No Currency").to_s
70 end
71
72 def status
73 plan.active? ? "Active" : "Expired"
74 end
75end
76
77class CustomerInfo
78 value_semantics do
79 plan_info Either(PlanInfo, PlanInfo::NoPlan)
80 tel Either(String, nil)
81 balance BigDecimal
82 cnam Either(String, nil)
83 calling_credit BigDecimal
84 calling_charges_this_month BigDecimal
85 end
86
87 def self.for(customer)
88 PromiseHash.all(
89 plan_info: PlanInfo.for(customer),
90 tel: customer.registered? ? customer.registered?.phone : nil,
91 balance: customer.balance,
92 cnam: customer.tndetails.dig(:features, :lidb, :subscriber_information),
93 calling_credit: customer.minute_limit.to_d,
94 calling_charges_this_month: customer.calling_charges_this_month
95 ).then(&method(:new))
96 end
97
98 def remaining_included_calling_credit
99 [calling_credit - calling_charges_this_month, 0].max
100 end
101
102 def form
103 FormTemplate.render("customer_info", info: self)
104 end
105end
106
107class AdminInfo
108 value_semantics do
109 jid ProxiedJID, coerce: ProxiedJID.method(:new)
110 customer_id String
111 fwd Either(CustomerFwd, nil)
112 info CustomerInfo
113 api API
114 call_info String
115 trust_level String
116 end
117
118 def self.for(
119 customer,
120 trust_level_repo: TrustLevelRepo.new,
121 call_attempt_repo: CallAttemptRepo.new
122 )
123 PromiseHash.all(
124 jid: customer.jid,
125 customer_id: customer.customer_id,
126 fwd: customer.fwd,
127 info: CustomerInfo.for(customer),
128 api: API.for(customer),
129 call_info: call_info(customer, call_attempt_repo),
130 trust_level: trust_level_repo.find(customer).then(&:to_s)
131 ).then(&method(:new))
132 end
133
134 class FakeLowBalance
135 def self.for(_)
136 self
137 end
138
139 def self.notify!
140 EMPromise.resolve(0)
141 end
142 end
143
144 def self.call_info(customer, call_attempt_repo)
145 if customer.registered?
146 call_attempt_repo
147 .find_outbound(customer, "+1", call_id: "dry_run")
148 .then(&:to_s)
149 else
150 EMPromise.resolve("No calling")
151 end
152 end
153
154 def form
155 FormTemplate.render("admin_info", admin_info: self)
156 end
157
158 def tel_link
159 [
160 "https://dashboard.bandwidth.com/portal/r/a",
161 CONFIG[:creds][:account],
162 "numbers/details",
163 info.tel.gsub(/\A\+1/, "")
164 ].join("/")
165 end
166end