diff --git a/.rubocop.yml b/.rubocop.yml index f46799f2d0b408ff96eef0863d5f947f496eae2a..f3d7110b10d3e19506b8972a504a1ec99d636981 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -24,7 +24,7 @@ Metrics/AbcSize: - test/* Metrics/ParameterLists: - Max: 6 + Max: 7 Naming/MethodParameterName: AllowNamesEndingInNumbers: false diff --git a/forms/customer_cdr.rb b/forms/customer_cdr.rb new file mode 100644 index 0000000000000000000000000000000000000000..f1183d053c7062e2b706da53402c9987ce592aa1 --- /dev/null +++ b/forms/customer_cdr.rb @@ -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" +) diff --git a/lib/cdr.rb b/lib/cdr.rb index 39cdaf6057bfb825e4e950fd7fc264ee0e868f53..c67a5dcd20586eaeec85021c12f3e70893b8a0b3 100644 --- a/lib/cdr.rb +++ b/lib/cdr.rb @@ -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 diff --git a/lib/cdr_repo.rb b/lib/cdr_repo.rb new file mode 100644 index 0000000000000000000000000000000000000000..7f5db2dac3f0c2206b8060419a1ee98336b15995 --- /dev/null +++ b/lib/cdr_repo.rb @@ -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 diff --git a/lib/command_list.rb b/lib/command_list.rb index fb87b7e831c761dcd5d8912173bd57885dc75de8..44965d0594040ea7c1cae2e406e0f08289ede58e 100644 --- a/lib/command_list.rb +++ b/lib/command_list.rb @@ -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 diff --git a/lib/customer.rb b/lib/customer.rb index 4aa96adc407bd603cf0778842faa35a013884b83..626be279d339d3e4b9fd70481fac18ea1d0c17c8 100644 --- a/lib/customer.rb +++ b/lib/customer.rb @@ -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 diff --git a/lib/customer_repo.rb b/lib/customer_repo.rb index 207a40510442b4908fe42f417ce522417bbf67ec..98a7e584e31872970fe2eb0eb93be140ebce3dff 100644 --- a/lib/customer_repo.rb +++ b/lib/customer_repo.rb @@ -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 diff --git a/sgx_jmp.rb b/sgx_jmp.rb index cefb94d9e0f1cbdf07076c6dcda67f8596a9f134..442010706fc56f9a9893de8de1a0f41350fecd02 100644 --- a/sgx_jmp.rb +++ b/sgx_jmp.rb @@ -526,6 +526,22 @@ Command.new( end }.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", diff --git a/test/test_helper.rb b/test/test_helper.rb index d6fdaceb8e8bc9d7593a6609fe450fd01524b5e0..260589f381e28ad473332702a11847273ed379ee 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -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) diff --git a/web.rb b/web.rb index ff12e696967411dfcbdadf52ee7460679057011f..52dff555cb9b9973c83a6bc64f8062efd641b237 100644 --- a/web.rb +++ b/web.rb @@ -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