Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions app/models/order.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,28 @@ def existing_transaction_type
order_transaction_type == :debit ? "buy" : "sell"
end

def sufficient_funds?
return true unless buy?
return true unless number_of_shares_valid_for_calculations?

current_balance_cents = (user.portfolio&.cash_balance || 0) * 100

fee_already_subtracted = user.orders.pending.exists? ? PortfolioTransaction::TRANSACTION_FEE_CENTS : 0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the purpose of this function here here is not calculating "if" the fee was already calculated but rather what fee we need to add

maybe a better name could be fee_to_charge

balance_without_reservations = current_balance_cents + purchase_cost + fee_already_subtracted

total_needed = purchase_cost + transaction_fee

balance_without_reservations >= total_needed
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to add an error here to alert the user to why the transaction fails if it does fail.
Can you add something like:
errors.add(:shares, "Insufficient funds. You have #{formatted_balance} but need #{formatted_cost}")?

end

def sufficient_shares?
return true unless sell?
return true unless number_of_shares_valid_for_calculations?

current_shares = user.portfolio&.shares_owned(stock_id) || 0
shares <= current_shares
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, we'll need to add an error if there aren't enough shares not just return false so we have an indication of why it didn't work.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes sense to me!

end

private

def number_of_shares_valid_for_calculations?
Expand Down
19 changes: 19 additions & 0 deletions app/services/execute_order.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ def self.execute(...)
def execute
return unless order.pending?

unless valid_order?
cancel_order!
return
end

ActiveRecord::Base.transaction do
create_portfolio_transaction
create_portfolio_stock
Expand Down Expand Up @@ -54,4 +59,18 @@ def update_order_status
def purchase_cost
order.purchase_cost
end

def valid_order?
if order.buy?
order.sufficient_funds?
elsif order.sell?
order.sufficient_shares?
end

true
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because this is outside of the if block this function will always return true and that isn't what we want we'd want it to return the result of sufficient_funds or sufficient_shares.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good callout I need to call return

end

def cancel_order!
order.cancel!
end
end
28 changes: 28 additions & 0 deletions test/services/execute_order_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,32 @@ class ExecuteOrderTest < ActiveSupport::TestCase
total_shares = portfolio.shares_owned(stock.id)
assert_equal 6, total_shares
end

test "cannot create buy order with insufficient funds" do
portfolio = create(:portfolio)
portfolio.portfolio_transactions.create!(amount_cents: 500, transaction_type: :deposit)
stock = create(:stock, price_cents: 100)

order = build(:order, :pending, :buy, shares: 10, stock: stock, user: portfolio.user)

assert_raises(ActiveRecord::RecordInvalid) do
order.save!
end

assert order.errors[:shares].first.include?("Insufficient funds")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question as below, is this the error message we will get?

end

test "cannot create sell order with insufficient shares" do
portfolio = create(:portfolio)
stock = create(:stock, price_cents: 100)
create(:portfolio_stock, portfolio: portfolio, stock: stock, shares: 5, purchase_price: 100)

order = build(:order, :pending, :sell, shares: 10, stock: stock, user: portfolio.user)

assert_raises(ActiveRecord::RecordInvalid) do
order.save!
end

assert order.errors[:base].first.include?("Cannot sell more shares than you own")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the error message we'd get I don't see it added as an error when we do the check.

end
end