Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
9 changes: 9 additions & 0 deletions app/Enums/DiscountType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace App\Enums;

enum DiscountType : string
{
case Percentage = 'percentage';
case FixedValue = 'fixed';
}
9 changes: 9 additions & 0 deletions app/Enums/UserRole.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace App\Enums;

enum UserRole : string
{
case Customer = 'customer';
case WholesaleCustomer = 'wholesale_customer';
}
28 changes: 28 additions & 0 deletions app/Http/Controllers/Auth/LoginController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class LoginController extends Controller
{
public function showLogin()
{
return view('auth.login');
}

public function authenticate(Request $request)
{
$request->validate([
'email' => ['required', 'email'],
'password' => ['required']
]);

if (auth()->attempt($request->only('email', 'password'))) {
return redirect()->route('products.index');
}

return back()->withErrors(['message' => 'Invalid credentials']);
}
}
16 changes: 16 additions & 0 deletions app/Http/Controllers/Auth/LogoutController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class LogoutController extends Controller
{
public function logout()
{
auth()->logout();

return redirect()->route('auth.login');
}
}
46 changes: 46 additions & 0 deletions app/Http/Controllers/ProductsController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

namespace App\Http\Controllers;

use App\Models\Product;
use App\Models\ProductCategory;
use App\PricingEngine\Data\CalculatedProduct;
use App\PricingEngine\Services\CalculatorService;
use Illuminate\Http\Request;

class ProductsController extends Controller
{
public function index(CalculatorService $calculatorService)
{
$products = Product::query()
->with('productCategory')
->get();

$calculatedProducts = $calculatorService->calculateForProducts(
products: $products,
user: auth()->user(),
);

$calculatedProductsByCategories = $calculatedProducts->mapToGroups(
fn(CalculatedProduct $calculatedProduct) => [
$calculatedProduct->getProduct()->productCategory->title => $calculatedProduct,
]
);

return view('products.index', [
'calculatedProductsByCategories' => $calculatedProductsByCategories,
]);
}

public function view(CalculatorService $calculatorService, Product $product)
{
$calculatedProduct = $calculatorService->calculateForProduct(
product: $product,
user: auth()->user(),
);

return view('products.view', [
'calculatedProduct' => $calculatedProduct
]);
}
}
23 changes: 23 additions & 0 deletions app/Models/Product.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Product extends Model
{
use HasFactory;

protected $fillable = [
'title',
'description',
'price',
];

public function productCategory(): BelongsTo
{
return $this->belongsTo(ProductCategory::class);
}
}
58 changes: 58 additions & 0 deletions app/Models/ProductCategory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

namespace App\Models;

use App\Enums\DiscountType;
use Brick\Money\Money;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;

class ProductCategory extends Model
{
use HasFactory;

protected $fillable = [
'title',
'discount_type',
'discount_value',
];

protected $appends = [
'formatted_discount'
];

protected function casts(): array
{
return [
'discount_type' => DiscountType::class,
];
}

public function products() : HasMany
{
return $this->hasMany(Product::class);
}

public function getFormattedDiscountAttribute(): string
{
return match ($this->discount_type) {
DiscountType::Percentage => "{$this->discount_value}%",
DiscountType::FixedValue => Money::ofMinor($this->discount_value, 'GBP')->formatTo('en_GB'),
default => 'No discount'
};
}

public function hasValidDiscount(): bool
{
if (empty($this->discount_type) || empty($this->discount_value)) {
return false;
}

if ($this->discount_value === 0 || $this->discount_value < 0) {
return false;
}

return true;
}
}
2 changes: 2 additions & 0 deletions app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace App\Models;

// use Illuminate\Contracts\Auth\MustVerifyEmail;
use App\Enums\UserRole;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
Expand Down Expand Up @@ -43,6 +44,7 @@ protected function casts(): array
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
'role' => UserRole::class,
];
}
}
35 changes: 35 additions & 0 deletions app/PricingEngine/Calculators/ProductCategoryCalculator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace App\PricingEngine\Calculators;

use App\Models\Product;
use App\Models\ProductCategory;
use App\Models\User;
use App\PricingEngine\Contracts\DiscountCalculatorInterface;
use App\PricingEngine\Data\PricingEngineResult;
use App\PricingEngine\Resolvers\DiscountTypeResolver;

class ProductCategoryCalculator implements DiscountCalculatorInterface
{
public function __construct(
private DiscountTypeResolver $discountTypeResolver
) {
}

public function apply(int $currentPrice, Product $product, ?User $user = null): int
{
/** @var ProductCategory $productCategory */
$productCategory = $product->productCategory;

if ($productCategory->hasValidDiscount() === false) {
return $currentPrice;
}

$discountType = $this->discountTypeResolver->resolve($productCategory->discount_type);

return $discountType->modify(
currentPrice: $currentPrice,
discountValue: $productCategory->discount_value
);
}
}
31 changes: 31 additions & 0 deletions app/PricingEngine/Calculators/UserCalculator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace App\PricingEngine\Calculators;

use App\Enums\UserRole;
use App\Models\Product;
use App\Models\User;
use App\PricingEngine\Contracts\DiscountCalculatorInterface;
use App\PricingEngine\Repositories\UserDiscountPercentageRepository;

class UserCalculator implements DiscountCalculatorInterface
{
public function __construct(
protected UserDiscountPercentageRepository $userDiscountPercentageRepository
) {
}

public function apply(int $currentPrice, Product $product, ?User $user = null): int
{
$userDiscount = $this->userDiscountPercentageRepository->getDiscountPercentage($user);

if ( ! $userDiscount) {
return $currentPrice;
}

return max(
0,
$currentPrice - ($currentPrice * ($userDiscount / 100))
);
}
}
11 changes: 11 additions & 0 deletions app/PricingEngine/Contracts/DiscountCalculatorInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace App\PricingEngine\Contracts;

use App\Models\Product;
use App\Models\User;

interface DiscountCalculatorInterface
{
public function apply(int $currentPrice, Product $product, ?User $user = null): int;
}
8 changes: 8 additions & 0 deletions app/PricingEngine/Contracts/DiscountTypeInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace App\PricingEngine\Contracts;

interface DiscountTypeInterface
{
public function modify(int $currentPrice, int $discountValue): int;
}
53 changes: 53 additions & 0 deletions app/PricingEngine/Data/CalculatedProduct.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace App\PricingEngine\Data;

use App\Models\Product;
use Brick\Money\Money;

readonly class CalculatedProduct
{
public function __construct(
public Product $product,
public int $calculatedPrice,
public int $calculatedDiscount,
) {
}

public function getProduct(): Product
{
return $this->product;
}

public function getCalculatedPrice(): int
{
return $this->calculatedPrice;
}

public function getCalculatedDiscount(): int
{
return $this->calculatedDiscount;
}

public function getDiscountPercentage(): int
{
return $this->product->price > 0
? (int)round(($this->calculatedDiscount / $this->product->price) * 100)
: 0;
}

public function getFormattedBasePrice(): string
{
return Money::ofMinor($this->product->price, 'GBP')->formatTo('en_GB');
}

public function getFormattedCalculatedPrice(): string
{
return Money::ofMinor($this->calculatedPrice, 'GBP')->formatTo('en_GB');
}

public function getFormattedDiscountPrice(): string
{
return Money::ofMinor($this->calculatedDiscount, 'GBP')->formatTo('en_GB');
}
}
16 changes: 16 additions & 0 deletions app/PricingEngine/DiscountTypes/FixedDiscountType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace App\PricingEngine\DiscountTypes;

use App\PricingEngine\Contracts\DiscountTypeInterface;

class FixedDiscountType implements DiscountTypeInterface
{
public function modify(int $currentPrice, int $discountValue): int
{
return max(
0,
$currentPrice - $discountValue
);
}
}
17 changes: 17 additions & 0 deletions app/PricingEngine/DiscountTypes/PercentageDiscountType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace App\PricingEngine\DiscountTypes;

use App\PricingEngine\Contracts\DiscountTypeInterface;
use Brick\Money\Money;

class PercentageDiscountType implements DiscountTypeInterface
{
public function modify(int $currentPrice, int $discountValue): int
{
return max(
0,
round($currentPrice - ($currentPrice * ($discountValue / 100)))
);
}
}
Loading