~singpolyma/sgx-jmp

eeb7208d13b06498ba150743ed2082ea3e781c99 — Stephen Paul Weber 6 months ago aab558d
Refactor CutomerPlan to use value semantics
3 files changed, 40 insertions(+), 46 deletions(-)

M lib/customer.rb
M lib/customer_plan.rb
M lib/plan.rb
M lib/customer.rb => lib/customer.rb +1 -1
@@ 84,7 84,7 @@ class Customer
	def with_plan(plan_name, **kwargs)
		self.class.new(
			@customer_id, @jid,
			plan: @plan.with_plan_name(plan_name, **kwargs),
			plan: @plan.with(plan_name: plan_name, **kwargs),
			balance: @balance, tndetails: @tndetails, sgx: @sgx
		)
	end

M lib/customer_plan.rb => lib/customer_plan.rb +37 -45
@@ 1,6 1,7 @@
# frozen_string_literal: true

require "forwardable"
require "value_semantics/monkey_patched"

require_relative "em"
require_relative "plan"


@@ 8,21 9,24 @@ require_relative "plan"
class CustomerPlan
	extend Forwardable

	attr_reader :expires_at, :auto_top_up_amount, :monthly_overage_limit,
	            :parent_customer_id

	def_delegator :@plan, :name, :plan_name
	def_delegators :@plan, :currency, :merchant_account, :monthly_price,
	def_delegator :plan, :name, :plan_name
	def_delegators :plan, :currency, :merchant_account, :monthly_price,
	               :minute_limit, :message_limit

	def self.for(customer_id, plan_name: nil, **kwargs)
		new(customer_id, plan: plan_name&.then(&Plan.method(:for)), **kwargs)
	value_semantics do
		customer_id           String
		plan                  Anything(), default: nil, coerce: true
		expires_at            Either(Time, nil), default_generator: -> { Time.now }
		auto_top_up_amount    Integer, default: 0
		monthly_overage_limit Integer, default: 0
		pending               Bool(), default: false
		parent_customer_id    Either(String, nil), default: nil
	end

	def self.default(customer_id, jid)
		config = CONFIG[:parented_domains][Blather::JID.new(jid).domain]
		if config
			self.for(
			new(
				customer_id,
				plan_name: config[:plan_name],
				parent_customer_id: config[:customer_id]


@@ 33,7 37,7 @@ class CustomerPlan
	end

	def self.extract(customer_id, **kwargs)
		self.for(
		new(
			customer_id,
			**kwargs.slice(
				:plan_name, :expires_at, :parent_customer_id, :pending,


@@ 42,46 46,32 @@ class CustomerPlan
		)
	end

	def initialize(
		customer_id,
		plan: nil,
		expires_at: Time.now,
		auto_top_up_amount: 0,
		monthly_overage_limit: 0,
		pending: false, parent_customer_id: nil
	)
		@customer_id = customer_id
		@plan = plan || OpenStruct.new
		@expires_at = expires_at
		@auto_top_up_amount = auto_top_up_amount || 0
		@monthly_overage_limit = monthly_overage_limit || 0
		@pending = pending
		@parent_customer_id = parent_customer_id
	def self.coerce_plan(plan_or_name_or_nil)
		return OpenStruct.new unless plan_or_name_or_nil

		Plan.for(plan_or_name_or_nil)
	end

	def initialize(customer_id=nil, **kwargs)
		kwargs[:plan] = kwargs.delete(:plan_name) if kwargs.key?(:plan_name)
		super(customer_id ? kwargs.merge(customer_id: customer_id) : kwargs)
	end

	def active?
		plan_name && @expires_at > Time.now
		plan_name && expires_at > Time.now
	end

	def status
		return :active if active?
		return :pending if @pending
		return :pending if pending

		:expired
	end

	def with_plan_name(plan_name, **kwargs)
		self.class.new(
			@customer_id,
			plan: Plan.for(plan_name),
			expires_at: @expires_at, **kwargs
		)
	end

	def verify_parent!
		return unless @parent_customer_id
		return unless parent_customer_id

		result = DB.query(<<~SQL, [@parent_customer_id])
		result = DB.query(<<~SQL, [parent_customer_id])
			SELECT plan_name FROM customer_plans WHERE customer_id=$1
		SQL



@@ 93,7 83,7 @@ class CustomerPlan

	def save_plan!
		verify_parent!
		DB.exec_defer(<<~SQL, [@customer_id, plan_name, @parent_customer_id])
		DB.exec_defer(<<~SQL, [customer_id, plan_name, parent_customer_id])
			INSERT INTO plan_log
				(customer_id, plan_name, parent_customer_id, date_range)
			VALUES (


@@ 122,7 112,7 @@ class CustomerPlan

	def activate_plan_starting_now
		verify_parent!
		activated = DB.exec(<<~SQL, [@customer_id, plan_name, @parent_customer_id])
		activated = DB.exec(<<~SQL, [customer_id, plan_name, parent_customer_id])
			INSERT INTO plan_log (customer_id, plan_name, date_range, parent_customer_id)
			VALUES ($1, $2, tsrange(LOCALTIMESTAMP, LOCALTIMESTAMP + '1 month'), $3)
			ON CONFLICT DO NOTHING


@@ 130,7 120,7 @@ class CustomerPlan
		activated = activated.cmd_tuples.positive?
		return false unless activated

		DB.exec(<<~SQL, [@customer_id])
		DB.exec(<<~SQL, [customer_id])
			DELETE FROM plan_log WHERE customer_id=$1 AND date_range << '[now,now]'
				AND upper(date_range) - lower(date_range) < '2 seconds'
		SQL


@@ 141,22 131,24 @@ class CustomerPlan
	end

	def activation_date
		DB.query_one(<<~SQL, @customer_id).then { |r| r[:start_date] }
		DB.query_one(<<~SQL, customer_id).then { |r| r[:start_date] }
			SELECT
				MIN(LOWER(date_range)) AS start_date
			FROM plan_log WHERE customer_id = $1;
		SQL
	end

	protected :customer_id, :plan, :pending, :[]

protected

	def charge_for_plan(note)
		raise "No plan setup" unless @plan
		raise "No plan setup" unless plan

		params = [
			@customer_id,
			"#{@customer_id}-bill-#{plan_name}-at-#{Time.now.to_i}",
			-@plan.monthly_price,
			customer_id,
			"#{customer_id}-bill-#{plan_name}-at-#{Time.now.to_i}",
			-plan.monthly_price,
			note
		]
		DB.exec(<<~SQL, params)


@@ 167,7 159,7 @@ protected
	end

	def add_one_month_to_current_plan
		DB.exec(<<~SQL, [@customer_id])
		DB.exec(<<~SQL, [customer_id])
			UPDATE plan_log SET date_range=range_merge(
				date_range,
				tsrange(

M lib/plan.rb => lib/plan.rb +2 -0
@@ 2,6 2,8 @@

class Plan
	def self.for(plan_name)
		return plan_name if plan_name.is_a?(Plan)

		plan = CONFIG[:plans].find { |p| p[:name] == plan_name }
		raise "No plan by that name" unless plan