Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create shop orders #272

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
29 changes: 15 additions & 14 deletions lib/cambiatus/orders.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ defmodule Cambiatus.Orders do
alias Cambiatus.Repo

alias Cambiatus.Orders.Order
alias Cambiatus.Shop.Product

@doc """
Returns the list of orders.
Expand Down Expand Up @@ -77,9 +78,11 @@ defmodule Cambiatus.Orders do
"""

# TODO: Add current_user to query
def get_shopping_cart() do
def get_shopping_cart(buyer) do
Order
|> where([o], o.status == "cart")
|> join(:left, [o], b in assoc(o, :buyer))
|> where([o, b], b.account == ^buyer.account)
Comment on lines +82 to +83
Copy link
Member

Choose a reason for hiding this comment

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

can't we simply preload the buyer? Something like this:

Suggested change
|> join(:left, [o], b in assoc(o, :buyer))
|> where([o, b], b.account == ^buyer.account)
|> Repo.preload(:buyer)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think so. Because this is getting an order where the status is cart and belongs to the current user. Since we're getting the order from these SQL statements I don't believe it's possible to preload the buyer.

|> Repo.all()
|> case do
[] ->
Expand Down Expand Up @@ -130,8 +133,8 @@ defmodule Cambiatus.Orders do
end

@doc """
Updates a order.

Updates an order.
MatheusBuss marked this conversation as resolved.
Show resolved Hide resolved
## Examples

iex> update_order(order, %{field: new_value})
Expand Down Expand Up @@ -245,21 +248,19 @@ defmodule Cambiatus.Orders do
"""
def create_item(attrs \\ %{})

def create_item(%{order_id: id} = attrs) do
def create_item(%{order_id: _id} = attrs) do
%Item{}
|> Item.changeset(attrs)
|> Repo.insert()
end

def create_item(attrs) do
case get_shopping_cart() do
def create_item(%{buyer: buyer} = attrs) do
case get_shopping_cart(buyer) do
{:error, "User has no shopping cart"} ->
cart_id =
create_order!()
|> Map.get(:id)
cart = create_order!()

attrs
|> Map.put(:order_id, cart_id)
|> Map.put(:order_id, cart.id)
|> create_item()

{:ok, order} ->
Expand All @@ -273,8 +274,8 @@ defmodule Cambiatus.Orders do
end

@doc """
Updates a item.

Updates an item.
## Examples

iex> update_item(item, %{field: new_value})
Expand All @@ -291,8 +292,8 @@ defmodule Cambiatus.Orders do
end

@doc """
Deletes a item.

Deletes an item.
## Examples

iex> delete_item(item)
Expand Down
20 changes: 19 additions & 1 deletion lib/cambiatus/orders/item.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ defmodule Cambiatus.Orders.Item do
alias Cambiatus.Shop.Product
alias Cambiatus.Accounts.User
alias Cambiatus.Orders.Order
alias Cambiatus.Orders
alias Cambiatus.Repo

schema "items" do
field(:units, :integer)
Expand All @@ -23,12 +25,28 @@ defmodule Cambiatus.Orders.Item do
end

@required_fields ~w(units unit_price status)a
@optional_fields ~w(shipping inserted_at updated_at)a
@optional_fields ~w(shipping inserted_at updated_at product_id order_id)a

@doc false
def changeset(item, attrs) do
item
|> cast(attrs, @required_fields ++ @optional_fields)
|> validate_required(@required_fields)
end

def validate_item_stock(%{id: id} = changeset) do
with {:ok, item} <- Orders.get_item(id),
item <- Repo.preload(item, :product),
product <- Map.get(item, :product) do
case product.track_stock do
MatheusBuss marked this conversation as resolved.
Show resolved Hide resolved
false ->
changeset

true ->
if product.units >= item.units,
do: changeset,
else: add_error(changeset, :units, "Not enough product in stock")
end
end
end
end
18 changes: 16 additions & 2 deletions lib/cambiatus/orders/order.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@ defmodule Cambiatus.Orders.Order do

alias Cambiatus.Accounts.User
alias Cambiatus.Orders.Item
alias Cambiatus.Orders

schema "orders" do
field(:payment_method, :string)
field(:payment_method, Ecto.Enum,
values: [:paypal, :bitcoin, :ethereum, :eos],
default: :paypal
)

field(:total, :float)
field(:status, :string)

Expand All @@ -18,12 +23,21 @@ defmodule Cambiatus.Orders.Order do
end

@required_fields ~w(payment_method total status)a
@optional_fields ~w(inserted_at updated_at)a
@optional_fields ~w(inserted_at updated_at buyer_id)a

@doc false
def changeset(order, attrs) do
order
|> cast(attrs, @required_fields ++ @optional_fields)
|> validate_required(@required_fields)
|> validate_checkout()
end

def validate_checkout(changeset) do
with order_id <- get_field(changeset, :id),
{:ok, order} <- Orders.get_order(order_id) do
if Map.get(order, :status) == "cart" and get_field(changeset, :status) == "checkout" do
Copy link
Member

Choose a reason for hiding this comment

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

This could be piped

Suggested change
with order_id <- get_field(changeset, :id),
{:ok, order} <- Orders.get_order(order_id) do
if Map.get(order, :status) == "cart" and get_field(changeset, :status) == "checkout" do
order_status =
changeset
|> get_field(:id)
|> Orders.get()
|> Map.get(:status)
if Enum.member(["cart", "checkout"], order_status) do
. . .

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The intent of this one is to check if the order stored in the db has the status cart and the incoming changeset is changing this status to checkout. Then we should make some more validations to ensure that everything is valid before the user is has the chance to make a payment.

This has also changed a bit. If you want to make another review it'll be very welcome.

Copy link
Member

Choose a reason for hiding this comment

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

This cart I've added a few comments about state machines, but we should leverage its structure to make sure its valid, instead of relying on manual verification.

If an order is on a certain state, we should trust our backend ingested it properly so its correct to have this state. This is an old article and probably outdated, but it contain precious concepts about this type of abstraction: https://medium.com/@joaomdmoura/state-machine-in-elixir-with-machinery-8ee6f9def2da

end
end
end
end
69 changes: 51 additions & 18 deletions test/cambiatus/orders_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ defmodule Cambiatus.OrdersTest do
assert Orders.get_order!(order.id) == order
end

test "create_order/1 with valid data creates a order" do
test "create_order/1 with valid data creates an order" do
assert {:ok, %Order{} = order} = Orders.create_order(@valid_attrs)
end

Expand All @@ -66,10 +66,28 @@ defmodule Cambiatus.OrdersTest do
assert_raise Ecto.NoResultsError, fn -> Orders.get_order!(order.id) end
end

test "change_order/1 returns a order changeset" do
test "change_order/1 returns an order changeset" do
order = order_fixture()
assert %Ecto.Changeset{} = Orders.change_order(order)
end

test "deleting an order deletes all items associated to it" do
order = insert(:order)
item1 = insert(:item, %{order: order})
item2 = insert(:item, %{order: order})
item3 = insert(:item, %{order: order})

order = Repo.preload(order, :items)

assert Enum.count(order.items) == 3

Orders.delete_order(order)

assert Orders.get_order(order.id) == {:error, "No order exists with the id: #{order.id}"}
assert Orders.get_item(item1.id) == {:error, "No item exists with the id: #{item1.id}"}
assert Orders.get_item(item2.id) == {:error, "No item exists with the id: #{item2.id}"}
assert Orders.get_item(item3.id) == {:error, "No item exists with the id: #{item3.id}"}
end
end

describe "items" do
Expand Down Expand Up @@ -101,44 +119,59 @@ defmodule Cambiatus.OrdersTest do
item
end

test "list_items/0 returns all items" do
item = item_fixture()
setup do
%{user: insert(:user)}
end

test "list_items/0 returns all items", %{user: user} do
item = item_fixture(%{buyer: user})
assert Orders.list_items() == [item]
end

test "get_item!/1 returns the item with given id" do
item = item_fixture()
test "get_item!/1 returns the item with given id", %{user: user} do
item = item_fixture(%{buyer: user})
assert Orders.get_item!(item.id) == item
end

test "create_item/1 with valid data creates a item" do
assert {:ok, %Item{} = item} = Orders.create_item(@valid_attrs)
test "create_item/1 with valid data creates an item", %{user: user} do
assert {:ok, %Item{} = item} = Orders.create_item(Map.merge(@valid_attrs, %{buyer: user}))
end

test "create_item/1 with invalid data returns error changeset" do
assert {:error, %Ecto.Changeset{}} = Orders.create_item(@invalid_attrs)
test "create_item/1 with invalid data returns error changeset", %{user: user} do
assert {:error, %Ecto.Changeset{}} =
Orders.create_item(Map.merge(@invalid_attrs, %{buyer: user}))
end

test "update_item/2 with valid data updates the item" do
item = item_fixture()
test "update_item/2 with valid data updates the item", %{user: user} do
item = item_fixture(%{buyer: user})
assert {:ok, %Item{} = item} = Orders.update_item(item, @update_attrs)
end

test "update_item/2 with invalid data returns error changeset" do
item = item_fixture()
test "update_item/2 with invalid data returns error changeset", %{user: user} do
item = item_fixture(%{buyer: user})
assert {:error, %Ecto.Changeset{}} = Orders.update_item(item, @invalid_attrs)
assert item == Orders.get_item!(item.id)
end

test "delete_item/1 deletes the item" do
item = item_fixture()
test "delete_item/1 deletes the item", %{user: user} do
item = item_fixture(%{buyer: user})
assert {:ok, %Item{}} = Orders.delete_item(item)
assert_raise Ecto.NoResultsError, fn -> Orders.get_item!(item.id) end
end

test "change_item/1 returns a item changeset" do
item = item_fixture()
test "change_item/1 returns an item changeset", %{user: user} do
item = item_fixture(%{buyer: user})
assert %Ecto.Changeset{} = Orders.change_item(item)
end

test "add item to existing cart", %{user: user} do
cart = insert(:order, %{status: "cart", buyer: user})

item =
item_fixture(%{buyer: user})
|> Repo.preload(:order)

assert item.order.id == cart.id
end
end
end