M .rubocop.yml => .rubocop.yml +1 -1
@@ 24,7 24,7 @@ Metrics/AbcSize:
- test/*
Metrics/ParameterLists:
- Max: 6
+ Max: 7
Naming/MethodParameterName:
AllowNamesEndingInNumbers: false
A forms/customer_cdr.rb => forms/customer_cdr.rb +13 -0
@@ 0,0 1,13 @@
+result!
+title "Call History"
+
+table(
+ @cdrs,
+ start: "Start",
+ direction: "Direction",
+ tel: "Number",
+ disposition: "Status",
+ duration: "Duration",
+ formatted_rate: "Rate",
+ formatted_charge: "Charge"
+)
M lib/cdr.rb => lib/cdr.rb +19 -9
@@ 30,7 30,25 @@ class CDR
billsec Integer
disposition Disposition
tel(/\A\+\d+\Z/)
- direction Either(:inbound, :outbound)
+ direction Either(:inbound, :outbound), coerce: :to_sym.to_proc
+ rate Either(nil, BigDecimal), default: nil
+ charge Either(nil, BigDecimal), default: nil
+ end
+
+ def formatted_rate
+ "$%.4f" % rate
+ end
+
+ def formatted_charge
+ "$%.4f" % charge
+ end
+
+ def duration
+ "%02d:%02d:%02d" % [
+ billsec / (60 * 60),
+ billsec % (60 * 60) / 60,
+ billsec % 60
+ ]
end
def self.for(event, **kwargs)
@@ 61,12 79,4 @@ class CDR
direction: :outbound
)
end
-
- def save
- columns, values = to_h.to_a.transpose
- DB.query_defer(<<~SQL, values)
- INSERT INTO cdr (#{columns.join(',')})
- VALUES ($1, $2, $3, $4, $5, $6, $7)
- SQL
- end
end
A lib/cdr_repo.rb => lib/cdr_repo.rb +47 -0
@@ 0,0 1,47 @@
+# frozen_string_literal: true
+
+require_relative "cdr"
+
+class CDRRepo
+ def initialize(db: DB)
+ @db = db
+ end
+
+ def put(cdr)
+ data = cdr.to_h
+ data.delete(:rate)
+ data.delete(:charge)
+ columns, values = data.to_a.transpose
+ DB.query_defer(<<~SQL, values)
+ INSERT INTO cdr (#{columns.join(',')})
+ VALUES ($1, $2, $3, $4, $5, $6, $7)
+ SQL
+ end
+
+ def find_range(customer, range)
+ customer_id = customer.customer_id
+ cdrs = @db.query_defer(CDR_SQL, [customer_id, range.first, range.last])
+
+ cdrs.then do |rows|
+ rows.map { |row|
+ CDR.new(**row.transform_keys(&:to_sym))
+ }
+ end
+ end
+
+ CDR_SQL = <<~SQL
+ SELECT
+ cdr_id,
+ customer_id,
+ start,
+ billsec,
+ disposition,
+ tel,
+ direction,
+ rate,
+ charge
+ FROM cdr_with_charge
+ WHERE customer_id = $1 AND start >= $2 AND DATE_TRUNC('day', start) <= $3
+ ORDER BY start DESC
+ SQL
+end
M lib/command_list.rb => lib/command_list.rb +1 -1
@@ 18,7 18,7 @@ class CommandList
args = {
from_jid: from_jid, customer: customer,
tel: customer&.registered? ? customer&.registered?&.phone : nil,
- fwd: customer&.fwd,
+ fwd: customer&.fwd, feature_flags: customer&.feature_flags || [],
payment_methods: []
}
return EMPromise.resolve(args) unless customer&.plan_name
M lib/customer.rb => lib/customer.rb +4 -2
@@ 18,7 18,7 @@ require_relative "./trivial_backend_sgx_repo"
class Customer
extend Forwardable
- attr_reader :customer_id, :balance, :jid, :tndetails
+ attr_reader :customer_id, :balance, :jid, :tndetails, :feature_flags
alias billing_customer_id customer_id
def_delegators :@plan, :active?, :activate_plan_starting_now, :bill_plan,
@@ 43,7 43,7 @@ class Customer
klass.new(
customer_id, jid,
plan: CustomerPlan.extract(customer_id, **kwargs),
- **kwargs.slice(:balance, :sgx, :tndetails, *keys)
+ **kwargs.slice(:balance, :sgx, :tndetails, :feature_flags, *keys)
)
end
@@ 53,6 53,7 @@ class Customer
plan: CustomerPlan.new(customer_id),
balance: BigDecimal(0),
tndetails: {},
+ feature_flags: [],
sgx: TrivialBackendSgxRepo.new.get(customer_id)
)
@plan = plan
@@ 62,6 63,7 @@ class Customer
@jid = jid
@balance = balance
@tndetails = tndetails
+ @feature_flags = feature_flags
@sgx = sgx
end
M lib/customer_repo.rb => lib/customer_repo.rb +8 -4
@@ 164,11 164,15 @@ protected
end
def fetch_redis(customer_id)
- mget(
- "jmp_customer_auto_top_up_amount-#{customer_id}",
- "jmp_customer_monthly_overage_limit-#{customer_id}"
- ).then { |r|
+ EMPromise.all([
+ mget(
+ "jmp_customer_auto_top_up_amount-#{customer_id}",
+ "jmp_customer_monthly_overage_limit-#{customer_id}"
+ ),
+ @redis.smembers("jmp_customer_feature_flags-#{customer_id}")
+ ]).then { |r, flags|
r.transform_keys { |k| k.match(/^jmp_customer_([^-]+)/)[1].to_sym }
+ .merge(feature_flags: flags.map(&:to_sym))
}
end
M sgx_jmp.rb => sgx_jmp.rb +16 -0
@@ 527,6 527,22 @@ Command.new(
}.register(self).then(&CommandList.method(:register))
Command.new(
+ "cdrs",
+ "๐ฒ Show Call Logs",
+ list_for: ->(feature_flags:, **) { feature_flags.include?(:cdrs) }
+) {
+ report_for = ((Date.today << 1)..Date.today)
+
+ Command.customer.then { |customer|
+ CDRRepo.new.find_range(customer, report_for)
+ }.then do |cdrs|
+ Command.finish do |reply|
+ reply.command << FormTemplate.render("customer_cdr", cdrs: cdrs)
+ end
+ end
+}.register(self).then(&CommandList.method(:register))
+
+Command.new(
"transactions",
"๐งพ Show Transactions",
list_for: ->(customer:, **) { !!customer&.currency }
M test/test_helper.rb => test/test_helper.rb +4 -0
@@ 258,6 258,10 @@ class FakeRedis
@values[key]&.size || 0
end
+ def smembers(key)
+ @values[key]&.to_a || []
+ end
+
def expire(_, _); end
def exists(*keys)
M web.rb => web.rb +7 -2
@@ 10,6 10,7 @@ require "sentry-ruby"
require_relative "lib/call_attempt_repo"
require_relative "lib/cdr"
+require_relative "lib/cdr_repo"
require_relative "lib/oob"
require_relative "lib/rev_ai"
require_relative "lib/roda_capture"
@@ 121,6 122,10 @@ class Web < Roda
opts[:call_attempt_repo] || CallAttemptRepo.new
end
+ def cdr_repo
+ opts[:cdr_repo] || CDRRepo.new
+ end
+
def rev_ai
RevAi.new(logger: log.child(loggable_params))
end
@@ 200,7 205,7 @@ class Web < Roda
end
customer_repo.find_by_tel(params["to"]).then do |customer|
- CDR.for_inbound(customer.customer_id, params).save
+ cdr_repo.put(CDR.for_inbound(customer.customer_id, params))
end
end
"OK"
@@ 359,7 364,7 @@ class Web < Roda
log.info "#{params['eventType']} #{params['callId']}", loggable_params
if params["eventType"] == "disconnect"
call_attempt_repo.ending_call(c, params["callId"])
- CDR.for_outbound(params).save.catch(&method(:log_error))
+ cdr_repo.put(CDR.for_outbound(params)).catch(&method(:log_error))
end
"OK"
end