Compare commits

...

2 Commits

6 changed files with 160 additions and 56 deletions

View File

@@ -73,8 +73,6 @@ if (! function_exists('sendSMS')) {
return [];
}
return [];
$response = Http::withToken('WsTEJTBGlQiJ')
->retry(
times: 3,

View File

@@ -7,6 +7,7 @@ use App\Http\Requests\CheckoutOrderRequest;
use App\Http\Resources\Api\V1\Order\OrderIndexResource;
use App\Models\Ecommerce\Product\Order\Order;
use App\Repositories\Ecommerce\Order\OrderRepository;
use App\Services\Order\CreateOrderService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@@ -35,9 +36,9 @@ class OrderController extends Controller
/**
* (*) 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;
if ($request->payment_type_id == 3) {

View File

@@ -1,58 +1,17 @@
<?php
declare(strict_types=1);
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 Illuminate\Support\Facades\DB;
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
*
* @return array<string, mixed>
*/
public static function availableTimes(): array
{

View File

@@ -1,11 +1,15 @@
<?php
declare(strict_types=1);
namespace App\Repositories\Ecommerce\Product;
use App\Helpers\Ecommerce\Product\Filter\ProductFilterer;
use App\Helpers\Ecommerce\Product\Sort\ProductSorter;
use App\Models\Ecommerce\Product\Product\Product;
use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@@ -21,7 +25,7 @@ class ProductRepository
*
* @var \Illuminate\Database\Eloquent\Builder
*/
protected mixed $queryBuilder;
protected Builder $queryBuilder;
/**
* Application request
@@ -31,7 +35,7 @@ class ProductRepository
/**
* Relationships to eager load
*
* @var array<int, string>
* @var array<int, string|array>
*/
protected array $with = [];
@@ -54,6 +58,8 @@ class ProductRepository
/**
* Update query builder with resource relationship
*
* @param mixed $resource
*/
public function queryAsFromResource($resource): self
{
@@ -119,7 +125,7 @@ class ProductRepository
public function applySearchQueries(): self
{
if (request()->filled('q')) {
$searcQuery = str_replace([
$searchQuery = str_replace([
'\\',
'(',
')',
@@ -128,7 +134,7 @@ class ProductRepository
], '', request('q'));
// Search by name
$this->queryBuilder->where('products.name', '~*', $searcQuery);
$this->queryBuilder->where('products.name', '~*', $searchQuery);
}
return $this;
@@ -145,7 +151,7 @@ class ProductRepository
}
/**
* "Where IN" clouse
* "Where IN" clause
*/
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
{
@@ -180,7 +186,7 @@ class ProductRepository
/**
* Get the results
*/
public function get()
public function get(): Collection
{
$this->eagerLoadRelationships();
@@ -257,6 +263,8 @@ class ProductRepository
/**
* Ajax paginate
*
* @param mixed $products
*/
public static function ajaxPaginate($products): JsonResponse
{

View 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;
});
}
}

View 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);
}
}