Detailed changes
@@ -13,6 +13,7 @@ field(
{ value: "bill_plan", label: "Bill Customer" },
{ value: "cancel_account", label: "Cancel Customer" },
{ value: "undo", label: "Undo" },
- { value: "reset_declines", label: "Reset Declines" }
+ { value: "reset_declines", label: "Reset Declines" },
+ { value: "set_trust_level", label: "Set Trust Level" }
]
)
@@ -0,0 +1,11 @@
+form!
+instructions "Set Trust Level"
+
+field(
+ var: "new_trust_level",
+ type: "list-single",
+ label: "Trust Level",
+ value: @manual || "automatic",
+ options: @levels.map { |lvl| { label: lvl, value: lvl } } +
+ [{ label: "Automatic", value: "automatic" }]
+)
@@ -0,0 +1,143 @@
+# frozen_string_literal: true
+
+require "value_semantics/monkey_patched"
+require_relative "../admin_action"
+require_relative "../form_to_h"
+require_relative "../trust_level_repo"
+
+class AdminAction
+ class SetTrustLevel < AdminAction
+ include Isomorphic
+
+ class Command
+ using FormToH
+
+ def self.for(target_customer, reply:)
+ TrustLevelRepo.new.find_manual(target_customer.customer_id).then { |man|
+ new(
+ man,
+ customer_id: target_customer.customer_id
+ )
+ }.then { |x|
+ reply.call(x.form).then(&x.method(:create))
+ }
+ end
+
+ def initialize(manual, **bag)
+ @manual = manual
+ @bag = bag.compact
+ end
+
+ def form
+ FormTemplate.render(
+ "admin_set_trust_level",
+ manual: @manual,
+ levels: TrustLevel.constants.map(&:to_s).reject { |x| x == "Manual" }
+ )
+ end
+
+ def create(result)
+ AdminAction::SetTrustLevel.for(
+ previous_trust_level: @manual,
+ **@bag,
+ **result.form.to_h
+ .reject { |_k, v| v == "automatic" }.transform_keys(&:to_sym)
+ )
+ end
+ end
+
+ InvalidLevel = Struct.new(:level, :levels) {
+ def to_s
+ "Trust level invalid: expected #{levels.join(', ')}, got #{level}"
+ end
+ }
+
+ NoMatch = Struct.new(:expected, :actual) {
+ def to_s
+ "Trust level doesn't match: expected #{expected}, got #{actual}"
+ end
+ }
+
+ def initialize(previous_trust_level: nil, new_trust_level: nil, **kwargs)
+ super(
+ previous_trust_level: previous_trust_level.presence,
+ new_trust_level: new_trust_level.presence,
+ **kwargs
+ )
+ end
+
+ def customer_id
+ @attributes[:customer_id]
+ end
+
+ def previous_trust_level
+ @attributes[:previous_trust_level]
+ end
+
+ def new_trust_level
+ @attributes[:new_trust_level]
+ end
+
+ # If I don't check previous_trust_level here I could get into this
+ # situation:
+ # 1. Set from automatic to Customer
+ # 2. Undo
+ # 3. Set from automatic to Paragon
+ # 4. Undo the undo (redo set from automatic to customer)
+ # Now if I don't check previous_trust_level we'll enqueue a thing that says
+ # we've set from manual to customer, but that's not actually what we did! We
+ # set from Paragon to customer. If I undo that now I won't end up back a
+ # paragon, I'll end up at automatic again, which isn't the state I was in a
+ # second ago
+ def check_forward
+ EMPromise.all([
+ check_noop,
+ check_valid,
+ check_consistent
+ ])
+ end
+
+ def forward
+ TrustLevelRepo.new.put(customer_id, new_trust_level).then { self }
+ end
+
+ def to_reverse
+ with(
+ previous_trust_level: new_trust_level,
+ new_trust_level: previous_trust_level
+ )
+ end
+
+ def to_s
+ "set_trust_level(#{customer_id}): "\
+ "#{pretty(previous_trust_level)} -> #{pretty(new_trust_level)}"
+ end
+
+ protected
+
+ def check_noop
+ EMPromise.reject(NoOp.new) if new_trust_level == previous_trust_level
+ end
+
+ def check_valid
+ options = TrustLevel.constants.map(&:to_s)
+ return unless new_trust_level && !options.include?(new_trust_level)
+
+ EMPromise.reject(InvalidLevel.new(new_trust_level, options))
+ end
+
+ def check_consistent
+ TrustLevelRepo.new.find_manual(customer_id).then { |trust|
+ unless previous_trust_level == trust
+ EMPromise.reject(
+ NoMatch.new(pretty(previous_trust_level), pretty(trust))
+ )
+ end
+ }
+ end
+
+ def pretty(level)
+ level || "automatic"
+ end
+ end
+end
@@ -4,6 +4,7 @@ require_relative "admin_action_repo"
require_relative "admin_actions/cancel"
require_relative "admin_actions/financial"
require_relative "admin_actions/reset_declines"
+require_relative "admin_actions/set_trust_level"
require_relative "bill_plan_command"
require_relative "customer_info_form"
require_relative "financial_info"
@@ -176,7 +177,8 @@ class AdminCommand
[:cancel_account, Simple.new(AdminAction::CancelCustomer)],
[:financial, Simple.new(AdminAction::Financial)],
[:undo, Undoable.new(Undo)],
- [:reset_declines, Undoable.new(AdminAction::ResetDeclines::Command)]
+ [:reset_declines, Undoable.new(AdminAction::ResetDeclines::Command)],
+ [:set_trust_level, Undoable.new(AdminAction::SetTrustLevel::Command)]
].each do |action, handler|
define_method("action_#{action}") do
handler.call(
@@ -1,5 +1,6 @@
# frozen_string_literal: true
+require "lazy_object"
require "value_semantics/monkey_patched"
require_relative "trust_level"
@@ -12,7 +13,7 @@ class TrustLevelRepo
def find(customer)
EMPromise.all([
- redis.get("jmp_customer_trust_level-#{customer.customer_id}"),
+ find_manual(customer.customer_id),
fetch_settled_amount(customer.customer_id)
]).then do |(manual, row)|
TrustLevel.for(
@@ -23,6 +24,18 @@ class TrustLevelRepo
end
end
+ def find_manual(customer_id)
+ redis.get("jmp_customer_trust_level-#{customer_id}")
+ end
+
+ def put(customer_id, trust_level)
+ if trust_level
+ redis.set("jmp_customer_trust_level-#{customer_id}", trust_level)
+ else
+ redis.del("jmp_customer_trust_level-#{customer_id}")
+ end
+ end
+
protected
def fetch_settled_amount(customer_id)