diff --git a/forms/admin_add_transaction.rb b/forms/admin_add_transaction.rb new file mode 100644 index 0000000000000000000000000000000000000000..358a1704e2755d20ea97f17f326319d2c5fc5665 --- /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 180a0bcea5d6ac026053c0e32ca0bb38b8069987..8623553d7ac036747ef2f4acd833a7c94459e5a7 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 0000000000000000000000000000000000000000..e243183063fccf76f14fbad85154e9ad151d45bf --- /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 4fbce24ac9b441391c9808ca47329befa1051203..c35d48a372cc154a319a09e72854e7ace5d4c01e 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 e4bce70f04bc4904ec08825e5e994a6aff4a41df..9ba47fce64776cf6e60d4e3b5539f609b1bb6f26 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