feat: add order management admin with list and detail views
Admin UI at /admin/orders to view, filter, and inspect orders. Adds list_orders/1 and count_orders_by_status/0 to the Orders context, status filter tabs, clickable order table with streams, and a detail page showing items, totals, and shipping address. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
65
test/simpleshop_theme/orders_test.exs
Normal file
65
test/simpleshop_theme/orders_test.exs
Normal file
@@ -0,0 +1,65 @@
|
||||
defmodule SimpleshopTheme.OrdersTest do
|
||||
use SimpleshopTheme.DataCase, async: false
|
||||
|
||||
alias SimpleshopTheme.Orders
|
||||
|
||||
import SimpleshopTheme.OrdersFixtures
|
||||
|
||||
describe "list_orders/1" do
|
||||
test "returns all orders" do
|
||||
order1 = order_fixture()
|
||||
order2 = order_fixture()
|
||||
|
||||
orders = Orders.list_orders()
|
||||
order_ids = Enum.map(orders, & &1.id)
|
||||
|
||||
assert order1.id in order_ids
|
||||
assert order2.id in order_ids
|
||||
assert length(orders) == 2
|
||||
end
|
||||
|
||||
test "filters by payment status" do
|
||||
_pending = order_fixture()
|
||||
paid = order_fixture(payment_status: "paid")
|
||||
_failed = order_fixture(payment_status: "failed")
|
||||
|
||||
orders = Orders.list_orders(status: "paid")
|
||||
assert length(orders) == 1
|
||||
assert hd(orders).id == paid.id
|
||||
end
|
||||
|
||||
test "returns all when status is 'all'" do
|
||||
order_fixture()
|
||||
order_fixture(payment_status: "paid")
|
||||
|
||||
orders = Orders.list_orders(status: "all")
|
||||
assert length(orders) == 2
|
||||
end
|
||||
|
||||
test "preloads items" do
|
||||
order_fixture()
|
||||
|
||||
[order] = Orders.list_orders()
|
||||
assert Ecto.assoc_loaded?(order.items)
|
||||
assert length(order.items) == 1
|
||||
end
|
||||
end
|
||||
|
||||
describe "count_orders_by_status/0" do
|
||||
test "returns empty map when no orders" do
|
||||
assert Orders.count_orders_by_status() == %{}
|
||||
end
|
||||
|
||||
test "counts orders by status" do
|
||||
order_fixture()
|
||||
order_fixture()
|
||||
order_fixture(payment_status: "paid")
|
||||
order_fixture(payment_status: "failed")
|
||||
|
||||
counts = Orders.count_orders_by_status()
|
||||
assert counts["pending"] == 2
|
||||
assert counts["paid"] == 1
|
||||
assert counts["failed"] == 1
|
||||
end
|
||||
end
|
||||
end
|
||||
119
test/simpleshop_theme_web/live/admin_live/orders_test.exs
Normal file
119
test/simpleshop_theme_web/live/admin_live/orders_test.exs
Normal file
@@ -0,0 +1,119 @@
|
||||
defmodule SimpleshopThemeWeb.AdminLive.OrdersTest do
|
||||
use SimpleshopThemeWeb.ConnCase, async: false
|
||||
|
||||
import Phoenix.LiveViewTest
|
||||
import SimpleshopTheme.AccountsFixtures
|
||||
import SimpleshopTheme.OrdersFixtures
|
||||
|
||||
setup do
|
||||
user = user_fixture()
|
||||
%{user: user}
|
||||
end
|
||||
|
||||
describe "unauthenticated" do
|
||||
test "redirects to login", %{conn: conn} do
|
||||
{:error, redirect} = live(conn, ~p"/admin/orders")
|
||||
assert {:redirect, %{to: path}} = redirect
|
||||
assert path == ~p"/users/log-in"
|
||||
end
|
||||
end
|
||||
|
||||
describe "order list" do
|
||||
setup %{conn: conn, user: user} do
|
||||
conn = log_in_user(conn, user)
|
||||
%{conn: conn}
|
||||
end
|
||||
|
||||
test "renders empty state when no orders", %{conn: conn} do
|
||||
{:ok, _view, html} = live(conn, ~p"/admin/orders")
|
||||
|
||||
assert html =~ "No orders yet"
|
||||
assert html =~ "Orders"
|
||||
end
|
||||
|
||||
test "renders orders table", %{conn: conn} do
|
||||
order = order_fixture(payment_status: "paid", customer_email: "test@shop.com")
|
||||
|
||||
{:ok, _view, html} = live(conn, ~p"/admin/orders")
|
||||
|
||||
assert html =~ order.order_number
|
||||
assert html =~ "test@shop.com"
|
||||
assert html =~ "paid"
|
||||
end
|
||||
|
||||
test "filters by status", %{conn: conn} do
|
||||
paid = order_fixture(payment_status: "paid")
|
||||
_pending = order_fixture()
|
||||
|
||||
{:ok, view, _html} = live(conn, ~p"/admin/orders")
|
||||
|
||||
html = render_click(view, "filter", %{"status" => "paid"})
|
||||
|
||||
assert html =~ paid.order_number
|
||||
end
|
||||
|
||||
test "navigates to order detail", %{conn: conn} do
|
||||
order = order_fixture(payment_status: "paid")
|
||||
|
||||
{:ok, _view, html} = live(conn, ~p"/admin/orders")
|
||||
|
||||
assert html =~ ~p"/admin/orders/#{order}"
|
||||
end
|
||||
end
|
||||
|
||||
describe "order detail" do
|
||||
setup %{conn: conn, user: user} do
|
||||
conn = log_in_user(conn, user)
|
||||
%{conn: conn}
|
||||
end
|
||||
|
||||
test "renders order details", %{conn: conn} do
|
||||
order = order_fixture(payment_status: "paid", customer_email: "buyer@example.com")
|
||||
|
||||
{:ok, _view, html} = live(conn, ~p"/admin/orders/#{order}")
|
||||
|
||||
assert html =~ order.order_number
|
||||
assert html =~ "buyer@example.com"
|
||||
assert html =~ "paid"
|
||||
assert html =~ "Order details"
|
||||
end
|
||||
|
||||
test "shows line items", %{conn: conn} do
|
||||
order = order_fixture(product_name: "Cool T-shirt", variant_title: "Blue / XL")
|
||||
|
||||
{:ok, _view, html} = live(conn, ~p"/admin/orders/#{order}")
|
||||
|
||||
assert html =~ "Cool T-shirt"
|
||||
assert html =~ "Blue / XL"
|
||||
end
|
||||
|
||||
test "shows shipping address", %{conn: conn} do
|
||||
order = order_fixture(payment_status: "paid")
|
||||
|
||||
{:ok, updated_order} =
|
||||
SimpleshopTheme.Orders.update_order(order, %{
|
||||
shipping_address: %{
|
||||
"name" => "Jane Doe",
|
||||
"line1" => "42 Test Street",
|
||||
"city" => "London",
|
||||
"postal_code" => "SW1A 1AA",
|
||||
"country" => "GB"
|
||||
}
|
||||
})
|
||||
|
||||
{:ok, _view, html} = live(conn, ~p"/admin/orders/#{updated_order}")
|
||||
|
||||
assert html =~ "Jane Doe"
|
||||
assert html =~ "42 Test Street"
|
||||
assert html =~ "London"
|
||||
assert html =~ "SW1A 1AA"
|
||||
end
|
||||
|
||||
test "redirects when order not found", %{conn: conn} do
|
||||
fake_id = Ecto.UUID.generate()
|
||||
|
||||
{:error, {:live_redirect, %{to: "/admin/orders"}}} =
|
||||
live(conn, ~p"/admin/orders/#{fake_id}")
|
||||
end
|
||||
end
|
||||
end
|
||||
42
test/support/fixtures/orders_fixtures.ex
Normal file
42
test/support/fixtures/orders_fixtures.ex
Normal file
@@ -0,0 +1,42 @@
|
||||
defmodule SimpleshopTheme.OrdersFixtures do
|
||||
@moduledoc """
|
||||
Test helpers for creating orders.
|
||||
"""
|
||||
|
||||
alias SimpleshopTheme.Orders
|
||||
|
||||
def order_fixture(attrs \\ %{}) do
|
||||
attrs = Enum.into(attrs, %{})
|
||||
|
||||
items = [
|
||||
%{
|
||||
variant_id: "var_#{System.unique_integer([:positive])}",
|
||||
name: Map.get(attrs, :product_name, "Test product"),
|
||||
variant: Map.get(attrs, :variant_title, "Red / Large"),
|
||||
price: Map.get(attrs, :unit_price, 1999),
|
||||
quantity: Map.get(attrs, :quantity, 1)
|
||||
}
|
||||
]
|
||||
|
||||
order_attrs = %{
|
||||
items: items,
|
||||
customer_email: Map.get(attrs, :customer_email, "customer@example.com"),
|
||||
currency: Map.get(attrs, :currency, "gbp")
|
||||
}
|
||||
|
||||
{:ok, order} = Orders.create_order(order_attrs)
|
||||
|
||||
case attrs[:payment_status] do
|
||||
"paid" ->
|
||||
{:ok, order} = Orders.mark_paid(order, "pi_test_#{System.unique_integer([:positive])}")
|
||||
order
|
||||
|
||||
"failed" ->
|
||||
{:ok, order} = Orders.mark_failed(order)
|
||||
order
|
||||
|
||||
_ ->
|
||||
order
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user