Refactor order creation logic by introducing CreateOrderService and removing the create method from OrderRepository. Update ProductRepository with stricter type declarations and fix minor typos in comments.
This commit is contained in:
@@ -7,6 +7,7 @@ use App\Http\Requests\CheckoutOrderRequest;
|
|||||||
use App\Http\Resources\Api\V1\Order\OrderIndexResource;
|
use App\Http\Resources\Api\V1\Order\OrderIndexResource;
|
||||||
use App\Models\Ecommerce\Product\Order\Order;
|
use App\Models\Ecommerce\Product\Order\Order;
|
||||||
use App\Repositories\Ecommerce\Order\OrderRepository;
|
use App\Repositories\Ecommerce\Order\OrderRepository;
|
||||||
|
use App\Services\Order\CreateOrderService;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
@@ -35,9 +36,9 @@ class OrderController extends Controller
|
|||||||
/**
|
/**
|
||||||
* (*) Store order
|
* (*) Store order
|
||||||
*/
|
*/
|
||||||
public function store(CheckoutOrderRequest $request): JsonResponse
|
public function store(CheckoutOrderRequest $request, CreateOrderService $service): JsonResponse
|
||||||
{
|
{
|
||||||
$order = (new OrderRepository($request->all()))->create();
|
$order = $service->execute(auth()->user(), $request->validated());
|
||||||
|
|
||||||
$url = null;
|
$url = null;
|
||||||
if ($request->payment_type_id == 3) {
|
if ($request->payment_type_id == 3) {
|
||||||
|
|||||||
@@ -1,58 +1,17 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Repositories\Ecommerce\Order;
|
namespace App\Repositories\Ecommerce\Order;
|
||||||
|
|
||||||
use App\Events\Ecommerce\Product\Order\OrderCreated;
|
|
||||||
use App\Models\Ecommerce\Product\Order\Order;
|
|
||||||
use App\Models\Ecommerce\Product\Order\Shipping\OrderShipping;
|
use App\Models\Ecommerce\Product\Order\Shipping\OrderShipping;
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
|
|
||||||
class OrderRepository
|
class OrderRepository
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Order repo
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
protected array $data = [],
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create new order
|
|
||||||
*/
|
|
||||||
public function create()
|
|
||||||
{
|
|
||||||
$order = Order::create($this->data);
|
|
||||||
|
|
||||||
auth()->user()->carts()->with(['product' => [
|
|
||||||
'media',
|
|
||||||
'channels',
|
|
||||||
]])->get()->each(function ($cart) use ($order) {
|
|
||||||
DB::table('order_items')->insert([
|
|
||||||
'product_name' => $cart->product->name,
|
|
||||||
'product_id' => $cart->product_id,
|
|
||||||
'order_id' => $order->id,
|
|
||||||
'channel_id' => $cart->product->channels->first()?->id ?? tmpostChannel()->id,
|
|
||||||
'quantity' => $cart->product_quantity,
|
|
||||||
'unit_price_amount' => $cart->product->price_amount,
|
|
||||||
'unit_cost_amount' => $cart->product->cost_amount,
|
|
||||||
'created_at' => now(),
|
|
||||||
'updated_at' => now(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$cart->product->update([
|
|
||||||
'stock' => $cart->product->stock - $cart->product_quantity,
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
auth()->user()->carts()->delete();
|
|
||||||
|
|
||||||
OrderCreated::dispatch($order);
|
|
||||||
|
|
||||||
return $order;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Available times
|
* Available times
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
public static function availableTimes(): array
|
public static function availableTimes(): array
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Repositories\Ecommerce\Product;
|
namespace App\Repositories\Ecommerce\Product;
|
||||||
|
|
||||||
use App\Helpers\Ecommerce\Product\Filter\ProductFilterer;
|
use App\Helpers\Ecommerce\Product\Filter\ProductFilterer;
|
||||||
use App\Helpers\Ecommerce\Product\Sort\ProductSorter;
|
use App\Helpers\Ecommerce\Product\Sort\ProductSorter;
|
||||||
use App\Models\Ecommerce\Product\Product\Product;
|
use App\Models\Ecommerce\Product\Product\Product;
|
||||||
use Illuminate\Contracts\Pagination\Paginator;
|
use Illuminate\Contracts\Pagination\Paginator;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
@@ -21,7 +25,7 @@ class ProductRepository
|
|||||||
*
|
*
|
||||||
* @var \Illuminate\Database\Eloquent\Builder
|
* @var \Illuminate\Database\Eloquent\Builder
|
||||||
*/
|
*/
|
||||||
protected mixed $queryBuilder;
|
protected Builder $queryBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Application request
|
* Application request
|
||||||
@@ -31,7 +35,7 @@ class ProductRepository
|
|||||||
/**
|
/**
|
||||||
* Relationships to eager load
|
* Relationships to eager load
|
||||||
*
|
*
|
||||||
* @var array<int, string>
|
* @var array<int, string|array>
|
||||||
*/
|
*/
|
||||||
protected array $with = [];
|
protected array $with = [];
|
||||||
|
|
||||||
@@ -54,6 +58,8 @@ class ProductRepository
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Update query builder with resource relationship
|
* Update query builder with resource relationship
|
||||||
|
*
|
||||||
|
* @param mixed $resource
|
||||||
*/
|
*/
|
||||||
public function queryAsFromResource($resource): self
|
public function queryAsFromResource($resource): self
|
||||||
{
|
{
|
||||||
@@ -119,7 +125,7 @@ class ProductRepository
|
|||||||
public function applySearchQueries(): self
|
public function applySearchQueries(): self
|
||||||
{
|
{
|
||||||
if (request()->filled('q')) {
|
if (request()->filled('q')) {
|
||||||
$searcQuery = str_replace([
|
$searchQuery = str_replace([
|
||||||
'\\',
|
'\\',
|
||||||
'(',
|
'(',
|
||||||
')',
|
')',
|
||||||
@@ -128,7 +134,7 @@ class ProductRepository
|
|||||||
], '', request('q'));
|
], '', request('q'));
|
||||||
|
|
||||||
// Search by name
|
// Search by name
|
||||||
$this->queryBuilder->where('products.name', '~*', $searcQuery);
|
$this->queryBuilder->where('products.name', '~*', $searchQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
@@ -145,7 +151,7 @@ class ProductRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* "Where IN" clouse
|
* "Where IN" clause
|
||||||
*/
|
*/
|
||||||
public function whereIn(string $attribute, array $value): self
|
public function whereIn(string $attribute, array $value): self
|
||||||
{
|
{
|
||||||
@@ -155,7 +161,7 @@ class ProductRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* "where integer in raw" clouse
|
* "where integer in raw" clause
|
||||||
*/
|
*/
|
||||||
public function whereIntegerInRaw(string $attribute, array $value): self
|
public function whereIntegerInRaw(string $attribute, array $value): self
|
||||||
{
|
{
|
||||||
@@ -180,7 +186,7 @@ class ProductRepository
|
|||||||
/**
|
/**
|
||||||
* Get the results
|
* Get the results
|
||||||
*/
|
*/
|
||||||
public function get()
|
public function get(): Collection
|
||||||
{
|
{
|
||||||
$this->eagerLoadRelationships();
|
$this->eagerLoadRelationships();
|
||||||
|
|
||||||
@@ -257,6 +263,8 @@ class ProductRepository
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Ajax paginate
|
* Ajax paginate
|
||||||
|
*
|
||||||
|
* @param mixed $products
|
||||||
*/
|
*/
|
||||||
public static function ajaxPaginate($products): JsonResponse
|
public static function ajaxPaginate($products): JsonResponse
|
||||||
{
|
{
|
||||||
|
|||||||
62
app/Services/Order/CreateOrderService.php
Normal file
62
app/Services/Order/CreateOrderService.php
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\Order;
|
||||||
|
|
||||||
|
use App\Events\Ecommerce\Product\Order\OrderCreated;
|
||||||
|
use App\Models\Ecommerce\Product\Order\Order;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class CreateOrderService
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create a new order for the user
|
||||||
|
*
|
||||||
|
* @param User $user
|
||||||
|
* @param array $data
|
||||||
|
* @return Order
|
||||||
|
*/
|
||||||
|
public function execute(User $user, array $data): Order
|
||||||
|
{
|
||||||
|
return DB::transaction(function () use ($user, $data) {
|
||||||
|
// 1. Create the order
|
||||||
|
$order = Order::create($data);
|
||||||
|
|
||||||
|
// 2. Process Cart Items
|
||||||
|
$user->carts()
|
||||||
|
->with(['product' => function ($query) {
|
||||||
|
$query->with(['media', 'channels']);
|
||||||
|
}])
|
||||||
|
->get()
|
||||||
|
->each(function ($cart) use ($order) {
|
||||||
|
// Create Order Item
|
||||||
|
DB::table('order_items')->insert([
|
||||||
|
'product_name' => $cart->product->name,
|
||||||
|
'product_id' => $cart->product_id,
|
||||||
|
'order_id' => $order->id,
|
||||||
|
'channel_id' => $cart->product->channels->first()?->id ?? tmpostChannel()->id,
|
||||||
|
'quantity' => $cart->product_quantity,
|
||||||
|
'unit_price_amount' => $cart->product->price_amount,
|
||||||
|
'unit_cost_amount' => $cart->product->cost_amount,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Update Stock
|
||||||
|
$cart->product->update([
|
||||||
|
'stock' => $cart->product->stock - $cart->product_quantity,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. Clear User Cart
|
||||||
|
$user->carts()->delete();
|
||||||
|
|
||||||
|
// 4. Dispatch Event
|
||||||
|
OrderCreated::dispatch($order);
|
||||||
|
|
||||||
|
return $order;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
76
tests/Feature/Services/Order/CreateOrderServiceTest.php
Normal file
76
tests/Feature/Services/Order/CreateOrderServiceTest.php
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Services\Order;
|
||||||
|
|
||||||
|
use App\Models\Ecommerce\Product\Cart\Cart;
|
||||||
|
use App\Models\Ecommerce\Product\Product\Product;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Services\Order\CreateOrderService;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Facades\Event;
|
||||||
|
use Tests\TestCase;
|
||||||
|
use App\Events\Ecommerce\Product\Order\OrderCreated;
|
||||||
|
|
||||||
|
class CreateOrderServiceTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
public function test_it_creates_order_successfully()
|
||||||
|
{
|
||||||
|
Event::fake();
|
||||||
|
|
||||||
|
// 1. Arrange
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$product = Product::factory()->create([
|
||||||
|
'stock' => 10,
|
||||||
|
'price_amount' => 100,
|
||||||
|
'cost_amount' => 50,
|
||||||
|
'name' => 'Test Product'
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Create Cart Item
|
||||||
|
Cart::factory()->create([
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'product_id' => $product->id,
|
||||||
|
'product_quantity' => 2
|
||||||
|
]);
|
||||||
|
|
||||||
|
$service = new CreateOrderService();
|
||||||
|
$orderData = [
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'status' => 'pending',
|
||||||
|
'total_amount' => 200
|
||||||
|
];
|
||||||
|
|
||||||
|
// 2. Act
|
||||||
|
$order = $service->execute($user, $orderData);
|
||||||
|
|
||||||
|
// 3. Assert
|
||||||
|
|
||||||
|
// Check Order Created
|
||||||
|
$this->assertDatabaseHas('orders', [
|
||||||
|
'id' => $order->id,
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'total_amount' => 200
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Check Order Items Created
|
||||||
|
$this->assertDatabaseHas('order_items', [
|
||||||
|
'order_id' => $order->id,
|
||||||
|
'product_id' => $product->id,
|
||||||
|
'quantity' => 2,
|
||||||
|
'product_name' => 'Test Product'
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Check Stock Deducted (10 - 2 = 8)
|
||||||
|
$this->assertEquals(8, $product->fresh()->stock);
|
||||||
|
|
||||||
|
// Check Cart Cleared
|
||||||
|
$this->assertDatabaseMissing('carts', [
|
||||||
|
'user_id' => $user->id
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Check Event Dispatched
|
||||||
|
Event::assertDispatched(OrderCreated::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user