Merge branch 'show-remaining-included-calling-credit'

Stephen Paul Weber created

* show-remaining-included-calling-credit:
  Show remaining included calling credit in customer info
  Add missing require

Change summary

forms/customer_info_partial.rb |  6 ++
lib/customer.rb                |  3 
lib/customer_info.rb           | 11 ++++
lib/customer_usage.rb          |  8 +++
test/test_customer_info.rb     | 86 ++++++++++++++++++++++++++++++++++++
5 files changed, 112 insertions(+), 2 deletions(-)

Detailed changes

forms/customer_info_partial.rb 🔗

@@ -26,4 +26,10 @@ field(
 	value: "$%.4f" % @info.balance
 )
 
+field(
+	var: "remaining_included_calling_credit",
+	label: "Remaining Included Calling Credit",
+	value: "$%.4f" % @info.remaining_included_calling_credit
+)
+
 render @info.plan_info.template

lib/customer.rb 🔗

@@ -27,7 +27,8 @@ class Customer
 	               :expires_at, :monthly_price, :save_plan!
 	def_delegators :@sgx, :deregister!, :register!, :registered?, :set_ogm_url,
 	               :fwd, :transcription_enabled
-	def_delegators :@usage, :usage_report, :message_usage, :incr_message_usage
+	def_delegators :@usage, :usage_report, :message_usage, :incr_message_usage,
+	               :calling_charges_this_month
 	def_delegators :@financials, :payment_methods, :btc_addresses,
 	               :add_btc_address, :declines, :mark_decline,
 	               :transactions

lib/customer_info.rb 🔗

@@ -6,6 +6,7 @@ require "relative_time"
 require "value_semantics/monkey_patched"
 
 require_relative "api"
+require_relative "call_attempt_repo"
 require_relative "customer"
 require_relative "customer_plan"
 require_relative "form_template"
@@ -79,6 +80,8 @@ class CustomerInfo
 		tel Either(String, nil)
 		balance BigDecimal
 		cnam Either(String, nil)
+		calling_credit BigDecimal
+		calling_charges_this_month BigDecimal
 	end
 
 	def self.for(customer)
@@ -86,10 +89,16 @@ class CustomerInfo
 			plan_info: PlanInfo.for(customer),
 			tel: customer.registered? ? customer.registered?.phone : nil,
 			balance: customer.balance,
-			cnam: customer.tndetails.dig(:features, :lidb, :subscriber_information)
+			cnam: customer.tndetails.dig(:features, :lidb, :subscriber_information),
+			calling_credit: customer.minute_limit.to_d,
+			calling_charges_this_month: customer.calling_charges_this_month
 		).then(&method(:new))
 	end
 
+	def remaining_included_calling_credit
+		[calling_credit - calling_charges_this_month, 0].max
+	end
+
 	def form
 		FormTemplate.render("customer_info", info: self)
 	end

lib/customer_usage.rb 🔗

@@ -72,4 +72,12 @@ class CustomerUsage
 			end
 		end
 	end
+
+	def calling_charges_this_month
+		DB.query_one(<<~SQL, @customer_id).then { |r| r[:charges] }
+			SELECT COALESCE(SUM(charge), 0) AS charges
+			FROM cdr_with_charge
+			WHERE customer_id=$1 AND start >= DATE_TRUNC('month', LOCALTIMESTAMP)
+		SQL
+	end
 end

test/test_customer_info.rb 🔗

@@ -6,7 +6,9 @@ require "trust_level_repo"
 require "trust_level"
 
 API::REDIS = FakeRedis.new
+CustomerPlan::DB = Minitest::Mock.new
 CustomerPlan::REDIS = Minitest::Mock.new
+CustomerUsage::DB = Minitest::Mock.new
 PlanInfo::DB = FakeDB.new(
 	["test"] => [
 		{
@@ -27,6 +29,12 @@ class CustomerInfoTest < Minitest::Test
 			[String, "test"]
 		)
 
+		CustomerUsage::DB.expect(
+			:query_one,
+			EMPromise.resolve({ charges: 0.to_d }),
+			[String, "test"]
+		)
+
 		cust = customer(sgx: sgx, plan_name: "test_usd")
 
 		assert CustomerInfo.for(cust).sync.form
@@ -34,6 +42,60 @@ class CustomerInfoTest < Minitest::Test
 	end
 	em :test_info_does_not_crash
 
+	def test_info_has_remaining_included_calling_credit
+		sgx = Minitest::Mock.new
+		sgx.expect(:registered?, false)
+
+		CustomerPlan::DB.expect(
+			:query_one,
+			EMPromise.resolve({ start_date: Time.now }),
+			[String, "test"]
+		)
+
+		CustomerUsage::DB.expect(
+			:query_one,
+			EMPromise.resolve({ charges: 0.044.to_d }),
+			[String, "test"]
+		)
+
+		cust = customer(sgx: sgx, plan_name: "test_usd")
+
+		assert_equal(
+			"$1.0000",
+			CustomerInfo.for(cust).sync.form
+				.field("remaining_included_calling_credit").value
+		)
+		assert_mock sgx
+	end
+	em :test_info_has_remaining_included_calling_credit
+
+	def test_info_out_of_remaining_included_calling_credit
+		sgx = Minitest::Mock.new
+		sgx.expect(:registered?, false)
+
+		CustomerPlan::DB.expect(
+			:query_one,
+			EMPromise.resolve({ start_date: Time.now }),
+			[String, "test"]
+		)
+
+		CustomerUsage::DB.expect(
+			:query_one,
+			EMPromise.resolve({ charges: 10.to_d }),
+			[String, "test"]
+		)
+
+		cust = customer(sgx: sgx, plan_name: "test_usd")
+
+		assert_equal(
+			"$0.0000",
+			CustomerInfo.for(cust).sync.form
+				.field("remaining_included_calling_credit").value
+		)
+		assert_mock sgx
+	end
+	em :test_info_out_of_remaining_included_calling_credit
+
 	def test_admin_info_does_not_crash
 		sgx = Minitest::Mock.new
 		sgx.expect(:registered?, false)
@@ -47,6 +109,12 @@ class CustomerInfoTest < Minitest::Test
 			[String, "test"]
 		)
 
+		CustomerUsage::DB.expect(
+			:query_one,
+			EMPromise.resolve({ charges: 0.to_d }),
+			[String, "test"]
+		)
+
 		cust = customer(sgx: sgx, plan_name: "test_usd")
 
 		trust_repo = Minitest::Mock.new
@@ -69,6 +137,12 @@ class CustomerInfoTest < Minitest::Test
 			[String, "test"]
 		)
 
+		CustomerUsage::DB.expect(
+			:query_one,
+			EMPromise.resolve({ charges: 0.to_d }),
+			[String, "test"]
+		)
+
 		cust = customer(sgx: sgx, plan_name: "test_usd")
 
 		call_attempt_repo = Minitest::Mock.new
@@ -92,6 +166,12 @@ class CustomerInfoTest < Minitest::Test
 	em :test_admin_info_with_tel_does_not_crash
 
 	def test_inactive_info_does_not_crash
+		CustomerUsage::DB.expect(
+			:query_one,
+			EMPromise.resolve({ charges: 0.to_d }),
+			[String, "test"]
+		)
+
 		sgx = Minitest::Mock.new
 		sgx.expect(:registered?, false)
 
@@ -108,6 +188,12 @@ class CustomerInfoTest < Minitest::Test
 	em :test_inactive_info_does_not_crash
 
 	def test_inactive_admin_info_does_not_crash
+		CustomerUsage::DB.expect(
+			:query_one,
+			EMPromise.resolve({ charges: 0.to_d }),
+			[String, "test"]
+		)
+
 		sgx = Minitest::Mock.new
 		sgx.expect(:registered?, false)
 		sgx.expect(:registered?, false)