A forms/admin_add_transaction.rb => forms/admin_add_transaction.rb +37 -0
@@ 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
+)
M => +2 -1
@@ 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" }
]
)
A lib/admin_actions/add_transaction.rb => lib/admin_actions/add_transaction.rb +138 -0
@@ 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
M lib/admin_command.rb => lib/admin_command.rb +3 -1
@@ 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(
M lib/transaction.rb => lib/transaction.rb +2 -1
@@ 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