1# frozen_string_literal: true
2
3require "digest"
4
5require_relative "./usage_report"
6
7class CustomerUsage
8 def initialize(customer_id)
9 @customer_id = customer_id
10 end
11
12 def usage_report(range)
13 EMPromise.all([
14 messages_by_day(range),
15 minutes_by_day(range)
16 ]).then do |args|
17 UsageReport.new(range, *args)
18 end
19 end
20
21 def expire_message_usage
22 today = Time.now.utc.to_date
23 REDIS.zremrangebylex(
24 "jmp_customer_outbound_messages-#{@customer_id}",
25 "-",
26 # Store message counts per day for 1 year
27 "[#{(today << 12).strftime('%Y%m%d')}"
28 )
29 end
30
31 def incr_message_usage(amount=1, body=nil)
32 today = Time.now.utc.to_date
33 EMPromise.all([
34 expire_message_usage,
35 REDIS.zincrby(
36 "jmp_customer_outbound_messages-#{@customer_id}",
37 amount,
38 today.strftime("%Y%m%d")
39 ),
40 incr_body(amount, body)
41 ]).then { |result| { today: result[1].to_i, body: result[2].to_i } }
42 end
43
44 def incr_body(amount, body)
45 # Short message like "hello" we can't block dupes
46 return 0 if body.to_s.length < 30
47
48 hash = Digest::SHA2.hexdigest(body[0..100])
49
50 EMPromise.all([
51 REDIS.incrby("jmp_outbound_body-#{hash}", amount),
52 REDIS.expire("jmp_outbound_body-#{hash}", 120)
53 ]).then(&:first)
54 end
55
56 def message_usage(range)
57 messages_by_day(range).then do |by_day|
58 by_day.values.sum
59 end
60 end
61
62 def messages_by_day(range)
63 EMPromise.all(range.first.downto(range.last).map { |day|
64 REDIS.zscore(
65 "jmp_customer_outbound_messages-#{@customer_id}",
66 day.strftime("%Y%m%d")
67 ).then { |c| [day, c.to_i] if c }
68 }).then { |r| r.compact.to_h.tap { |h| h.default = 0 } }
69 end
70
71 QUERY_FOR_MINUTES = <<~SQL
72 SELECT
73 date_trunc('day', start)::date as day,
74 CEIL(SUM(billsec)/60.0)::integer as minutes
75 FROM cdr
76 WHERE customer_id=$1 and start >= $3 and date_trunc('day', start) <= $2
77 GROUP BY date_trunc('day', start);
78 SQL
79
80 def minutes_by_day(range)
81 DB.query_defer(
82 QUERY_FOR_MINUTES,
83 [@customer_id, range.first, range.last]
84 ).then do |result|
85 result.each_with_object(Hash.new(0)) do |row, minutes|
86 minutes[row["day"]] = row["minutes"]
87 end
88 end
89 end
90
91 def calling_charges_this_month
92 DB.query_one(<<~SQL, @customer_id).then { |r| r[:charges] }
93 SELECT COALESCE(SUM(charge), 0) AS charges
94 FROM cdr_with_charge
95 WHERE customer_id=$1 AND start >= DATE_TRUNC('month', LOCALTIMESTAMP)
96 SQL
97 end
98end