From 67611fad04149aa539725e1205b4eda22ff42498 Mon Sep 17 00:00:00 2001 From: Christopher Vollick <0@psycoti.ca> Date: Tue, 24 Jan 2023 11:04:44 -0500 Subject: [PATCH] Command to Manually Add Money to Account An admin can now add a transaction to an account without having to log into the DB. A few notes: - The transaction ID allows a "%" in it which gets substituted with a unique value. This is so if you've got a transaction value already, like an Interac Transfer or something, you can just put it here. But if I'm making something up like "cash" I don't have to mash the keyboard just to get a good ID. I can just use "cash_%" and be content that I'll get a good value - The notes have a few prefilled values, which is just there for convenience and consistency. They're an open list, though, for manual things. Except on clients that don't support open lists... - There's an option to notify the user. I haven't built that in this commit and will come later. This is so that under normal operation we don't have to message from support and tell them "hey, we've got your money", and even better we don't have to tell them "hey, we've got your money, you may want to go talk to the bot to activate". But if support is already talking to them, we can disable it and tell them things in a more organic way. Like I said, I haven't built that in this commit, though. So, this is a start, at least. --- forms/admin_add_transaction.rb | 37 +++++++ forms/admin_menu.rb | 3 +- lib/admin_actions/add_transaction.rb | 138 +++++++++++++++++++++++++++ lib/admin_command.rb | 4 +- lib/transaction.rb | 3 +- 5 files changed, 182 insertions(+), 3 deletions(-) create mode 100644 forms/admin_add_transaction.rb create mode 100644 lib/admin_actions/add_transaction.rb diff --git a/forms/admin_add_transaction.rb b/forms/admin_add_transaction.rb new file mode 100644 index 0000000..358a170 --- /dev/null +++ b/forms/admin_add_transaction.rb @@ -0,0 +1,37 @@ +form! +instructions "Add Transaction" + +field( + var: "transaction_id", + type: "text-single", + label: "Transaction ID", + description: "a % will be replaced with a unique value" +) + +field( + var: "amount", + type: "text-single", + datatype: "xs:decimal", + label: "Amount" +) + +field( + var: "note", + type: "list-single", + open: true, + label: "Note", + options: [ + { value: "Bitcoin payment" }, + { value: "Cash" }, + { value: "Interac e-Transfer" }, + { value: "Bitcoin Cash" }, + { value: "PayPal Migration Bonus" } + ] +) + +field( + var: "bonus_eligible?", + type: "boolean", + label: "Compute bonus?", + value: 1 +) diff --git a/forms/admin_menu.rb b/forms/admin_menu.rb index 180a0bc..8623553 100644 --- a/forms/admin_menu.rb +++ b/forms/admin_menu.rb @@ -23,6 +23,7 @@ field( { value: "reset_declines", label: "Reset Declines" }, { value: "set_trust_level", label: "Set Trust Level" }, { value: "add_invites", label: "Add Invites" }, - { value: "number_change", label: "Number Change" } + { value: "number_change", label: "Number Change" }, + { value: "add_transaction", label: "Add Transaction" } ] ) diff --git a/lib/admin_actions/add_transaction.rb b/lib/admin_actions/add_transaction.rb new file mode 100644 index 0000000..e243183 --- /dev/null +++ b/lib/admin_actions/add_transaction.rb @@ -0,0 +1,138 @@ +# frozen_string_literal: true + +require "bigdecimal/util" +require "securerandom" +require "time" +require "value_semantics/monkey_patched" + +require_relative "../admin_action" +require_relative "../form_to_h" + +class AdminAction + class AddTransaction < AdminAction + class Command + using FormToH + + def self.for(target_customer, reply:) + time = DateTime.now.iso8601 + EMPromise.resolve( + new( + customer_id: target_customer.customer_id, + created_at: time, settled_after: time + ) + ).then { |x| + reply.call(x.form).then(&x.method(:create)) + } + end + + def initialize(**bag) + @bag = bag + end + + def form + FormTemplate.render("admin_add_transaction") + end + + def create(result) + hash = result.form.to_h + .reject { |_k, v| v == "nil" }.transform_keys(&:to_sym) + hash[:transaction_id] = hash[:transaction_id] + .sub("%", SecureRandom.uuid) + + AdminAction::AddTransaction.for( + **@bag, + **hash + ) + end + end + + TransactionExists = Struct.new(:transaction_id) do + def to_s + "The transaction #{transaction_id} already exists" + end + end + + TransactionDoesNotExist = Struct.new(:transaction_id) do + def to_s + "The transaction #{transaction_id} doesn't exist" + end + end + + def customer_id + @attributes[:customer_id] + end + + def amount + @attributes[:amount].to_d + end + + def transaction_id + @attributes[:transaction_id] + end + + def created_at + @attributes[:created_at] + end + + def settled_after + @attributes[:settled_after] + end + + def note + @attributes[:note] + end + + def bonus_eligible? + ["1", "true"].include?(@attributes[:bonus_eligible?]) + end + + def transaction + @transaction ||= Transaction.new( + **@attributes.slice(:customer_id, :transaction_id, :amount, :note), + created_at: created_at, settled_after: settled_after, + bonus_eligible?: bonus_eligible? + ) + end + + def check_forward + EMPromise.resolve(nil) + .then { check_noop } + .then { transaction.exists? } + .then { |e| + EMPromise.reject(TransactionExists.new(transaction_id)) if e + } + end + + def check_reverse + EMPromise.resolve(nil) + .then { check_noop } + .then { transaction.exists? } + .then { |e| + EMPromise.reject(TransactionDoesNotExist.new(transaction_id)) unless e + } + end + + def to_s + "add_transaction(#{customer_id}): #{note} (#{transaction_id}) "\ + "#{transaction}" + end + + def forward + transaction.insert.then { + self + } + end + + def reverse + transaction.delete.then { + self + } + end + + protected + + def check_noop + EMPromise.reject(NoOp.new) if amount.zero? + end + end +end diff --git a/lib/admin_command.rb b/lib/admin_command.rb index 4fbce24..c35d48a 100644 --- a/lib/admin_command.rb +++ b/lib/admin_command.rb @@ -2,6 +2,7 @@ require_relative "admin_action_repo" require_relative "admin_actions/add_invites" +require_relative "admin_actions/add_transaction" require_relative "admin_actions/cancel" require_relative "admin_actions/financial" require_relative "admin_actions/reset_declines" @@ -178,7 +179,8 @@ class AdminCommand [:reset_declines, Undoable.new(AdminAction::ResetDeclines::Command)], [:set_trust_level, Undoable.new(AdminAction::SetTrustLevel::Command)], [:add_invites, Undoable.new(AdminAction::AddInvites::Command)], - [:number_change, Undoable.new(AdminAction::NumberChange::Command)] + [:number_change, Undoable.new(AdminAction::NumberChange::Command)], + [:add_transaction, Undoable.new(AdminAction::AddTransaction::Command)] ].each do |action, handler| define_method("action_#{action}") do handler.call( diff --git a/lib/transaction.rb b/lib/transaction.rb index e4bce70..9ba47fc 100644 --- a/lib/transaction.rb +++ b/lib/transaction.rb @@ -12,6 +12,7 @@ class Transaction settled_after Time, coerce: ->(x) { Time.parse(x.to_s) } amount BigDecimal, coerce: ->(x) { BigDecimal(x, 4) } note String + bonus_eligible? Bool(), default: true end def insert @@ -42,7 +43,7 @@ class Transaction end def bonus - return BigDecimal(0) if amount <= 15 + return BigDecimal(0) unless bonus_eligible? && amount > 15 amount * case amount -- 2.38.5