diff --git a/lib/customer.rb b/lib/customer.rb index 409f0f3460990e2bb0538e6ca1e75ba48af5933f..6bf09b46c73a373c91062562c965dd28024930d2 100644 --- a/lib/customer.rb +++ b/lib/customer.rb @@ -4,6 +4,7 @@ require "forwardable" require_relative "./blather_ext" require_relative "./customer_plan" +require_relative "./customer_usage" require_relative "./backend_sgx" require_relative "./ibr" require_relative "./payment_methods" @@ -47,6 +48,7 @@ class Customer def_delegators :@plan, :active?, :activate_plan_starting_now, :bill_plan, :currency, :merchant_account, :plan_name def_delegators :@sgx, :register!, :registered? + def_delegator :@usage, :report, :usage_report def initialize( customer_id, @@ -60,6 +62,7 @@ class Customer plan: plan_name && Plan.for(plan_name), expires_at: expires_at ) + @usage = CustomerUsage.new(customer_id) @customer_id = customer_id @balance = balance @sgx = sgx diff --git a/lib/customer_usage.rb b/lib/customer_usage.rb new file mode 100644 index 0000000000000000000000000000000000000000..b43dc8e3938f069839d2096940067e6941e3fc89 --- /dev/null +++ b/lib/customer_usage.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require_relative "./usage_report" + +class CustomerUsage + def initialize(customer_id) + @customer_id = customer_id + end + + def report(range) + EMPromise.all([ + messages_by_day(range), + minutes_by_day(range) + ]).then do |args| + UsageReport.new(range, *args) + end + end + + def messages_by_day(range) + EMPromise.all(range.first.downto(range.last).map { |day| + REDIS.zscore( + "jmp_customer_outbound_messages-#{@customer_id}", + day.strftime("%Y%m%d") + ).then { |c| [day, c.to_i] if c } + }).then { |r| Hash[r.compact].tap { |h| h.default = 0 } } + end + + QUERY_FOR_MINUTES = <<~SQL + SELECT + date_trunc('day', start)::date as day, + CEIL(SUM(billsec)/60.0)::integer as minutes + FROM cdr + WHERE customer_id=$1 and start >= $3 and start < $2 + GROUP BY date_trunc('day', start); + SQL + + def minutes_by_day(range) + DB.query_defer( + QUERY_FOR_MINUTES, + [@customer_id, range.first, range.last] + ).then do |result| + result.each_with_object(Hash.new(0)) do |row, minutes| + minutes[row["day"]] = row["minutes"] + end + end + end +end diff --git a/lib/form_table.rb b/lib/form_table.rb new file mode 100644 index 0000000000000000000000000000000000000000..b1f120ace6194a82f5c18a48e365abb766d8682c --- /dev/null +++ b/lib/form_table.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +class FormTable + def initialize(rows, **cols) + @cols = cols + @rows = rows + end + + def add_to_form(form) + Nokogiri::XML::Builder.with(form) do |xml| + xml.reported do + @cols.each do |var, label| + xml.field(var: var.to_s, label: label.to_s) + end + end + + add_rows_to_xml(xml) + end + end + +protected + + def add_rows_to_xml(xml) + @rows.each do |row| + xml.item do + row.each.with_index do |val, idx| + xml.field(var: @cols.keys[idx].to_s) do + xml.value val.to_s + end + end + end + end + end +end diff --git a/lib/usage_report.rb b/lib/usage_report.rb new file mode 100644 index 0000000000000000000000000000000000000000..8e36db63469cdb320a2148d17c6c07b1a0d29b72 --- /dev/null +++ b/lib/usage_report.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require_relative "./form_table" + +class UsageReport + def initialize(report_for, messages, minutes) + @report_for = report_for + @messages = messages + @minutes = minutes + end + + def ==(other) + report_for == other.report_for && + messages == other.messages && + minutes == other.minutes + end + + def form + form = Blather::Stanza::X.new(:result) + form.title = + form.instructions = + "Usage from #{report_for.first} to #{report_for.last}" + form_table.add_to_form(form) + form + end + + def form_table + total_messages = 0 + total_minutes = 0 + + FormTable.new( + @report_for.first.downto(@report_for.last).map do |day| + total_messages += @messages[day] + total_minutes += @minutes[day] + [day, @messages[day], @minutes[day]] + end + [["Total", total_messages, total_minutes]], + day: "Day", messages: "Messages", minutes: "Minutes" + ) + end + +protected + + attr_reader :report_for, :messages, :minutes +end diff --git a/sgx_jmp.rb b/sgx_jmp.rb index 39f2967303cffd8c61ea999fcf63a8a7a4a7e69f..642429f72235f46211008f6ab5c0c22706d18c4e 100644 --- a/sgx_jmp.rb +++ b/sgx_jmp.rb @@ -253,6 +253,11 @@ disco_items node: "http://jabber.org/protocol/commands" do |iq| iq.to, "jabber:iq:register", "Register" + ), + Blather::Stanza::DiscoItems::Item.new( + iq.to, + "usage", + "Show Monthly Usage" ) ] self << reply @@ -316,6 +321,25 @@ command :execute?, node: "buy-credit", sessionid: nil do |iq| }.catch { |e| panic(e, sentry_hub) } end +command :execute?, node: "usage", sessionid: nil do |iq| + sentry_hub = new_sentry_hub(iq, name: iq.node) + report_for = (Date.today..(Date.today << 1)) + + Customer.for_jid(iq.from.stripped).then { |customer| + sentry_hub.current_scope.set_user( + id: customer.customer_id, + jid: iq.from.stripped.to_s + ) + + customer.usage_report(report_for) + }.then { |usage_report| + reply = iq.reply + reply.status = :completed + reply.command << usage_report.form + BLATHER << reply + }.catch { |e| panic(e, sentry_hub) } +end + command :execute?, node: "web-register", sessionid: nil do |iq| sentry_hub = new_sentry_hub(iq, name: iq.node) diff --git a/test/test_customer.rb b/test/test_customer.rb index bbe812c1ffcee1cabeb16a8175ef68dfe0defa51..e391084e7c301298d114d36cd21012d8ecf24b73 100644 --- a/test/test_customer.rb +++ b/test/test_customer.rb @@ -8,6 +8,8 @@ Customer::BRAINTREE = Minitest::Mock.new Customer::REDIS = Minitest::Mock.new Customer::DB = Minitest::Mock.new CustomerPlan::DB = Minitest::Mock.new +CustomerUsage::REDIS = Minitest::Mock.new +CustomerUsage::DB = Minitest::Mock.new class CustomerTest < Minitest::Test def test_for_jid @@ -176,4 +178,31 @@ class CustomerTest < Minitest::Test Customer.new("test").stanza_from(m) Customer::BLATHER.verify end + + def test_customer_usage_report + report_for = (Date.today..(Date.today - 1)) + report_for.first.downto(report_for.last).each.with_index do |day, idx| + CustomerUsage::REDIS.expect( + :zscore, + EMPromise.resolve(idx), + ["jmp_customer_outbound_messages-test", day.strftime("%Y%m%d")] + ) + end + CustomerUsage::DB.expect( + :query_defer, + EMPromise.resolve([{ "day" => report_for.first, "minutes" => 123 }]), + [String, ["test", report_for.first, report_for.last]] + ) + assert_equal( + UsageReport.new( + report_for, { + Date.today => 0, + (Date.today - 1) => 1 + }, + Date.today => 123 + ), + Customer.new("test").usage_report(report_for).sync + ) + end + em :test_customer_usage_report end