From c576c9af5f24f40fe0da06dde51a297a3ae3c756 Mon Sep 17 00:00:00 2001 From: Christopher Vollick <0@psycoti.ca> Date: Tue, 7 Jun 2022 11:29:01 -0400 Subject: [PATCH] SetTrustLevel Command Here we have a form for the extra information we need. They say they want to set the trust level, we ask which one they'd like, and then we make the command that does that. This involves adding a new method to the TrustLevel to get just the manual level, so I can tell the difference between being set to Customer manually (in the form) or being automatically determined to be Customer (which means the form should be set to automatic). I also obviously need the method to set a new trust level too. --- forms/admin_menu.rb | 3 +- forms/admin_set_trust_level.rb | 11 +++ lib/admin_actions/set_trust_level.rb | 143 +++++++++++++++++++++++++++ lib/admin_command.rb | 4 +- lib/trust_level_repo.rb | 15 ++- 5 files changed, 173 insertions(+), 3 deletions(-) create mode 100644 forms/admin_set_trust_level.rb create mode 100644 lib/admin_actions/set_trust_level.rb diff --git a/forms/admin_menu.rb b/forms/admin_menu.rb index dc65dca7201cb80281809cf6ee60ccbcc60bba01..c4f50c16ddc94f2b5e1c994bdd28a309434b9296 100644 --- a/forms/admin_menu.rb +++ b/forms/admin_menu.rb @@ -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" } ] ) diff --git a/forms/admin_set_trust_level.rb b/forms/admin_set_trust_level.rb new file mode 100644 index 0000000000000000000000000000000000000000..3f8a74a52a2408d795e91020e7afe5a491e00bfc --- /dev/null +++ b/forms/admin_set_trust_level.rb @@ -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" }] +) diff --git a/lib/admin_actions/set_trust_level.rb b/lib/admin_actions/set_trust_level.rb new file mode 100644 index 0000000000000000000000000000000000000000..3ffb20f688195e9fbf026ea8b695ed95be4e205a --- /dev/null +++ b/lib/admin_actions/set_trust_level.rb @@ -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 diff --git a/lib/admin_command.rb b/lib/admin_command.rb index 8e721c61787d7a2ed77eb2f324a535c051f5d4a4..9c82e05f30a7c6d8a91a8cc711447917035ce40f 100644 --- a/lib/admin_command.rb +++ b/lib/admin_command.rb @@ -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( diff --git a/lib/trust_level_repo.rb b/lib/trust_level_repo.rb index 74fbecbc8df820f3f34e4e97529ceff49647b264..6c52e27bc6e8c01a8296b2bf5d058b1073046265 100644 --- a/lib/trust_level_repo.rb +++ b/lib/trust_level_repo.rb @@ -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)