first upload all files

This commit is contained in:
NW
2023-06-11 13:14:03 +01:00
parent f14dbc52b5
commit c08b36d1b6
1705 changed files with 106852 additions and 0 deletions

View File

@@ -0,0 +1,23 @@
<?php
namespace Modules\Coupon\Admin;
use Modules\Admin\Ui\AdminTable;
class CouponTable extends AdminTable
{
/**
* Make table response for the resource.
*
* @return \Illuminate\Http\JsonResponse
*/
public function make()
{
return $this->newTable()
->addColumn('discount', function ($coupon) {
return $coupon->is_percent
? "{$coupon->value}%"
: $coupon->value->format();
});
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace Modules\Coupon\Admin;
use Modules\Admin\Ui\Tab;
use Modules\Admin\Ui\Tabs;
use Modules\Coupon\Entities\Coupon;
use Modules\Category\Entities\Category;
class CouponTabs extends Tabs
{
public function make()
{
$this->group('coupon_information', trans('coupon::coupons.tabs.group.coupon_information'))
->active()
->add($this->general())
->add($this->usageRestrictions())
->add($this->usageLimits());
}
public function general()
{
return tap(new Tab('general', trans('coupon::coupons.tabs.general')), function (Tab $tab) {
$tab->active();
$tab->weight(5);
$tab->fields([
'name',
'code',
'is_percent',
'value',
'free_shipping',
'start_date',
'end_date',
'is_active',
]);
$tab->view('coupon::admin.coupons.tabs.general');
});
}
public function usageRestrictions()
{
return tap(new Tab('usage_restrictions', trans('coupon::coupons.tabs.usage_restrictions')), function (Tab $tab) {
$tab->weight(10);
$tab->fields(['minimum_spend']);
$coupon = Coupon::withoutGlobalScope('active')->findOrNew(request('id'));
$tab->view('coupon::admin.coupons.tabs.usage_restrictions', [
'products' => $coupon->productList(),
'excludeProducts' => $coupon->excludeProductList(),
'categories' => Category::treeList(),
]);
});
}
private function usageLimits()
{
return tap(new Tab('usage_limits', trans('coupon::coupons.tabs.usage_limits')), function (Tab $tab) {
$tab->weight(15);
$tab->fields(['usage_limit_per_coupon', 'usage_limit_per_customer']);
$tab->view('coupon::admin.coupons.tabs.usage_limits');
});
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Modules\Coupon\Checkers;
use Closure;
use Modules\Cart\Facades\Cart;
use Modules\Coupon\Exceptions\CouponAlreadyAppliedException;
class AlreadyApplied
{
public function handle($coupon, Closure $next)
{
if (Cart::couponAlreadyApplied($coupon)) {
throw new CouponAlreadyAppliedException;
}
return $next($coupon);
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace Modules\Coupon\Checkers;
use Closure;
use Modules\Cart\Facades\Cart;
use Modules\Coupon\Exceptions\InapplicableCouponException;
class ApplicableCategories
{
public function handle($coupon, Closure $next)
{
$coupon->load('categories');
if ($coupon->categories->isEmpty()) {
return $next($coupon);
}
$cartItems = Cart::items()->filter(function ($cartItem) use ($coupon) {
return $coupon->categories->intersect($cartItem->product->categories)->isNotEmpty();
});
if ($cartItems->isEmpty()) {
throw new InapplicableCouponException;
}
return $next($coupon);
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace Modules\Coupon\Checkers;
use Closure;
use Modules\Cart\Facades\Cart;
use Modules\Coupon\Exceptions\InapplicableCouponException;
class ApplicableProducts
{
public function handle($coupon, Closure $next)
{
$coupon->load('products');
if ($coupon->products->isEmpty()) {
return $next($coupon);
}
$cartItems = Cart::items()->filter(function ($cartItem) use ($coupon) {
return $coupon->products->contains($cartItem->product);
});
if ($cartItems->isEmpty()) {
throw new InapplicableCouponException;
}
return $next($coupon);
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Modules\Coupon\Checkers;
use Closure;
use Modules\Coupon\Exceptions\CouponNotExistsException;
class CouponExists
{
public function handle($coupon, Closure $next)
{
if (is_null($coupon)) {
throw new CouponNotExistsException;
}
return $next($coupon);
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Modules\Coupon\Checkers;
use Closure;
use Modules\Cart\Facades\Cart;
use Modules\Coupon\Exceptions\InapplicableCouponException;
class ExcludedCategories
{
public function handle($coupon, Closure $next)
{
$coupon->load('excludeCategories');
if ($coupon->excludeCategories->isEmpty()) {
return $next($coupon);
}
foreach (Cart::items() as $cartItem) {
if ($this->inExcludedCategories($coupon, $cartItem)) {
throw new InapplicableCouponException;
}
}
return $next($coupon);
}
private function inExcludedCategories($coupon, $cartItem)
{
return $coupon->excludeCategories->intersect($cartItem->product->categories)->isNotEmpty();
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Modules\Coupon\Checkers;
use Closure;
use Modules\Cart\Facades\Cart;
use Modules\Coupon\Exceptions\InapplicableCouponException;
class ExcludedProducts
{
public function handle($coupon, Closure $next)
{
$coupon->load('excludeProducts');
if ($coupon->excludeProducts->isEmpty()) {
return $next($coupon);
}
foreach (Cart::items() as $cartItem) {
if ($this->inExcludedProducts($coupon, $cartItem)) {
throw new InapplicableCouponException;
}
}
return $next($coupon);
}
private function inExcludedProducts($coupon, $cartItem)
{
return $coupon->excludeProducts->contains($cartItem->product);
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Modules\Coupon\Checkers;
use Closure;
use Modules\Coupon\Exceptions\MaximumSpendException;
class MaximumSpend
{
public function handle($coupon, Closure $next)
{
if ($coupon->spentMoreThanMaximumAmount()) {
throw new MaximumSpendException($coupon->maximum_spend);
}
return $next($coupon);
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Modules\Coupon\Checkers;
use Closure;
use Modules\Coupon\Exceptions\MinimumSpendException;
class MinimumSpend
{
public function handle($coupon, Closure $next)
{
if ($coupon->didNotSpendTheRequiredAmount()) {
throw new MinimumSpendException($coupon->minimum_spend);
}
return $next($coupon);
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Modules\Coupon\Checkers;
use Closure;
use Modules\Coupon\Exceptions\CouponUsageLimitReachedException;
class UsageLimitPerCoupon
{
public function handle($coupon, Closure $next)
{
if ($coupon->usageLimitReached()) {
throw new CouponUsageLimitReachedException;
}
return $next($coupon);
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Modules\Coupon\Checkers;
use Closure;
use Modules\Coupon\Exceptions\CouponUsageLimitReachedException;
class UsageLimitPerCustomer
{
public function handle($coupon, Closure $next)
{
if ($coupon->perCustomerUsageLimitReached()) {
throw new CouponUsageLimitReachedException;
}
return $next($coupon);
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Modules\Coupon\Checkers;
use Closure;
use Modules\Coupon\Exceptions\InvalidCouponException;
class ValidCoupon
{
public function handle($coupon, Closure $next)
{
if ($coupon->invalid()) {
throw new InvalidCouponException;
}
return $next($coupon);
}
}

View File

@@ -0,0 +1,10 @@
<?php
return [
'admin.coupons' => [
'index' => 'coupon::permissions.index',
'create' => 'coupon::permissions.create',
'edit' => 'coupon::permissions.edit',
'destroy' => 'coupon::permissions.destroy',
],
];

View File

@@ -0,0 +1,13 @@
<?php
use Modules\Coupon\Entities\Coupon;
$factory->define(Coupon::class, function (Faker\Generator $faker) {
return [
'name' => $faker->word(),
'code' => $faker->word(),
'value' => $faker->randomNumber(),
'is_percent' => $faker->boolean(),
'is_active' => $faker->boolean(),
];
});

View File

@@ -0,0 +1,44 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateCouponsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('coupons', function (Blueprint $table) {
$table->increments('id');
$table->string('code')->index();
$table->decimal('value', 18, 4)->unsigned()->nullable();
$table->boolean('is_percent');
$table->boolean('free_shipping');
$table->decimal('minimum_spend', 18, 4)->unsigned()->nullable();
$table->decimal('maximum_spend', 18, 4)->unsigned()->nullable();
$table->integer('usage_limit_per_coupon')->unsigned()->nullable();
$table->integer('usage_limit_per_customer')->unsigned()->nullable();
$table->integer('used')->default(0);
$table->boolean('is_active');
$table->date('start_date')->nullable();
$table->date('end_date')->nullable();
$table->softDeletes();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('coupons');
}
}

View File

@@ -0,0 +1,36 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateCouponTranslationsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('coupon_translations', function (Blueprint $table) {
$table->increments('id');
$table->integer('coupon_id')->unsigned();
$table->string('locale');
$table->string('name');
$table->unique(['coupon_id', 'locale']);
$table->foreign('coupon_id')->references('id')->on('coupons')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('coupon_translations');
}
}

View File

@@ -0,0 +1,36 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateCouponProductsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('coupon_products', function (Blueprint $table) {
$table->integer('coupon_id')->unsigned();
$table->integer('product_id')->unsigned();
$table->boolean('exclude')->default(false);
$table->primary(['coupon_id', 'product_id']);
$table->foreign('coupon_id')->references('id')->on('coupons')->onDelete('cascade');
$table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('coupon_products');
}
}

View File

@@ -0,0 +1,36 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateCouponCategoriesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('coupon_categories', function (Blueprint $table) {
$table->integer('coupon_id')->unsigned();
$table->integer('category_id')->unsigned();
$table->boolean('exclude')->default(false);
$table->primary(['coupon_id', 'category_id', 'exclude']);
$table->foreign('coupon_id')->references('id')->on('coupons')->onDelete('cascade');
$table->foreign('category_id')->references('id')->on('categories')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('coupon_categories');
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Modules\Coupon\Database\Seeders;
use Illuminate\Database\Seeder;
use Modules\Coupon\Entities\Coupon;
class CouponDatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
factory(Coupon::class, 10)->create();
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace Modules\Coupon\Entities\Concerns;
trait RelationList
{
/**
* Get the list of the coupon applicable products.
*
* @return array
*/
public function productList()
{
return $this->getItemList('products');
}
/**
* Get the list of the excluded products.
*
* @return array
*/
public function excludeProductList()
{
return $this->getItemList('excludeProducts');
}
/**
* Get the item list for the given coupon with the given attribute.
*
* @param string $attributes
* @return array
*/
private function getItemList($attribute)
{
$items = $this->getAttribute($attribute);
return $items->mapWithKeys(function ($item) {
return [$item->id => $item->name];
})->all();
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace Modules\Coupon\Entities\Concerns;
trait SyncRelations
{
protected function syncProducts($products)
{
$this->products()->sync(
$this->makeSyncList($products, ['exclude' => false])
);
}
protected function syncExcludeProducts($excludeProducts)
{
$this->excludeProducts()->sync(
$this->makeSyncList($excludeProducts, ['exclude' => true])
);
}
protected function syncCategories($categories)
{
$this->categories()->sync(
$this->makeSyncList($categories, ['exclude' => false])
);
}
protected function syncExcludeCategories($excludeCategories)
{
$this->excludeCategories()->sync(
$this->makeSyncList($excludeCategories, ['exclude' => true])
);
}
private function makeSyncList($items, $pivotData)
{
$pivotData = array_fill(0, count($items), $pivotData);
return array_combine($items, $pivotData);
}
}

View File

@@ -0,0 +1,297 @@
<?php
namespace Modules\Coupon\Entities;
use Modules\Support\Money;
use Modules\Cart\Facades\Cart;
use Modules\User\Entities\User;
use Modules\Order\Entities\Order;
use Illuminate\Support\Facades\DB;
use Modules\Support\Eloquent\Model;
use Modules\Coupon\Admin\CouponTable;
use Modules\Product\Entities\Product;
use Modules\Category\Entities\Category;
use Modules\Support\Eloquent\Translatable;
use Illuminate\Database\Eloquent\SoftDeletes;
class Coupon extends Model
{
use Translatable,
SoftDeletes,
Concerns\SyncRelations,
Concerns\RelationList;
/**
* The relations to eager load on every query.
*
* @var array
*/
protected $with = ['translations'];
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'code',
'is_percent',
'value',
'free_shipping',
'start_date',
'end_date',
'is_active',
'minimum_spend',
'maximum_spend',
'usage_limit_per_coupon',
'usage_limit_per_customer',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'free_shipping' => 'boolean',
'is_active' => 'boolean',
];
/**
* The attributes that should be mutated to dates.
*
* @var array
*/
protected $dates = ['start_date', 'end_date', 'deleted_at'];
/**
* The attributes that are translatable.
*
* @var array
*/
protected $translatedAttributes = ['name'];
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted()
{
static::saved(function ($coupon) {
$coupon->saveRelations(request()->all());
});
static::addActiveGlobalScope();
}
public static function findByCode($code)
{
return self::where(DB::raw('BINARY `code`'), $code)->first();
}
public function freeShipping()
{
return $this->free_shipping ? $this : (object) ['free_shipping' => 0];
}
public function valid()
{
if ($this->hasStartDate() && $this->hasEndDate()) {
return $this->startDateIsValid() && $this->endDateIsValid();
}
if ($this->hasStartDate()) {
return $this->startDateIsValid();
}
if ($this->hasEndDate()) {
return $this->endDateIsValid();
}
return true;
}
public function invalid()
{
return ! $this->valid();
}
private function hasStartDate()
{
return ! is_null($this->start_date);
}
private function hasEndDate()
{
return ! is_null($this->end_date);
}
private function startDateIsValid()
{
return today() >= $this->start_date;
}
private function endDateIsValid()
{
return today() <= $this->end_date;
}
public function usageLimitReached($customerEmail = null)
{
return $this->perCouponUsageLimitReached() || $this->perCustomerUsageLimitReached($customerEmail);
}
public function perCouponUsageLimitReached()
{
if (is_null($this->usage_limit_per_coupon)) {
return false;
}
return $this->used >= $this->usage_limit_per_coupon;
}
public function perCustomerUsageLimitReached($customerEmail = null)
{
if ($this->couponHasNoUsageLimitForCustomers() ||
$this->userIsNotLoggedInWhenAddingCouponToCart($customerEmail)
) {
return false;
}
$customerEmail = $customerEmail ?: auth()->user()->email;
$used = $this->orders()
->where('customer_email', $customerEmail)
->count();
return $used >= $this->usage_limit_per_customer;
}
private function couponHasNoUsageLimitForCustomers()
{
return is_null($this->usage_limit_per_customer);
}
private function userIsNotLoggedInWhenAddingCouponToCart($customerEmail = null)
{
return is_null($customerEmail) && auth()->guest();
}
public function didNotSpendTheRequiredAmount()
{
if (is_null($this->minimum_spend)) {
return false;
}
return Cart::subTotal()->lessThan($this->minimum_spend);
}
public function spentMoreThanMaximumAmount()
{
if (is_null($this->maximum_spend)) {
return false;
}
return Cart::subTotal()->greaterThan($this->maximum_spend);
}
public function products()
{
return $this->belongsToMany(Product::class, 'coupon_products')
->withPivot('exclude')
->wherePivot('exclude', false);
}
public function excludeProducts()
{
return $this->belongsToMany(Product::class, 'coupon_products')
->withPivot('exclude')
->wherePivot('exclude', true);
}
public function categories()
{
return $this->belongsToMany(Category::class, 'coupon_categories')
->withPivot('exclude')
->wherePivot('exclude', false);
}
public function excludeCategories()
{
return $this->belongsToMany(Category::class, 'coupon_categories')
->withPivot('exclude')
->wherePivot('exclude', true);
}
public function orders()
{
return $this->hasMany(Order::class)->withTrashed();
}
public function customers()
{
return $this->hasManyThrough(
User::class,
Order::class,
'coupon_id',
'id',
'id',
'customer_id'
)->withTrashed();
}
public function getValueAttribute($value)
{
if ($this->is_percent) {
return $value;
}
return Money::inDefaultCurrency($value);
}
public function getMinimumSpendAttribute($minimumSpend)
{
if (! is_null($minimumSpend)) {
return Money::inDefaultCurrency($minimumSpend);
}
}
public function getMaximumSpendAttribute($maximumSpend)
{
if (! is_null($maximumSpend)) {
return Money::inDefaultCurrency($maximumSpend);
}
}
public function getTotalAttribute($total)
{
return Money::inDefaultCurrency($total);
}
/**
* Get table data for the resource
*
* @return \Illuminate\Http\JsonResponse
*/
public function table()
{
return new CouponTable($this->newQuery()->withoutGlobalScope('active'));
}
/**
* Save associated relations for the coupon.
*
* @param array $attributes
* @return void
*/
public function saveRelations(array $attributes)
{
$this->syncProducts(array_get($attributes, 'products', []));
$this->syncExcludeProducts(array_get($attributes, 'exclude_products', []));
$this->syncCategories(array_get($attributes, 'categories', []));
$this->syncExcludeCategories(array_get($attributes, 'exclude_categories', []));
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace Modules\Coupon\Entities;
use Modules\Support\Eloquent\TranslationModel;
class CouponTranslation extends TranslationModel
{
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = ['name'];
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Modules\Coupon\Exceptions;
use Exception;
class CouponAlreadyAppliedException extends Exception
{
/**
* Render the exception into an HTTP response.
*
* @return \Illuminate\Http\Response
*/
public function render()
{
return response()->json([
'message' => trans('coupon::messages.already_applied'),
], 403);
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Modules\Coupon\Exceptions;
use Exception;
class CouponNotExistsException extends Exception
{
/**
* Render the exception into an HTTP response.
*
* @return \Illuminate\Http\Response
*/
public function render()
{
return response()->json([
'message' => trans('coupon::messages.not_exists'),
], 404);
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Modules\Coupon\Exceptions;
use Exception;
class CouponUsageLimitReachedException extends Exception
{
/**
* Render the exception into an HTTP response.
*
* @return \Illuminate\Http\Response
*/
public function render()
{
return response()->json([
'message' => trans('coupon::messages.usage_limit_reached'),
], 403);
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Modules\Coupon\Exceptions;
use Exception;
class InapplicableCouponException extends Exception
{
/**
* Render the exception into an HTTP response.
*
* @return \Illuminate\Http\Response
*/
public function render()
{
return response()->json([
'message' => trans('coupon::messages.inapplicable'),
], 403);
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Modules\Coupon\Exceptions;
use Exception;
class InvalidCouponException extends Exception
{
/**
* Render the exception into an HTTP response.
*
* @return \Illuminate\Http\Response
*/
public function render()
{
return response()->json([
'message' => trans('coupon::messages.invalid_coupon'),
], 403);
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Modules\Coupon\Exceptions;
use Exception;
class MaximumSpendException extends Exception
{
/**
* The maximum amount that is allowed.
*
* @var \Modules\Support\Money
*/
private $money;
/**
* Create a new instance of the exceptions
*
* @param \Modules\Support\Money $money
*/
public function __construct($money)
{
$this->money = $money;
}
/**
* Render the exception into an HTTP response.
*
* @return \Illuminate\Http\Response
*/
public function render()
{
return response()->json([
'message' => trans('coupon::messages.maximum_spend', ['amount' => $this->money->convertToCurrentCurrency()->format()]),
], 403);
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Modules\Coupon\Exceptions;
use Exception;
class MinimumSpendException extends Exception
{
/**
* The amount that need to spend.
*
* @var \Modules\Support\Money
*/
private $money;
/**
* Create a new instance of the exceptions
*
* @param \Modules\Support\Money $money
*/
public function __construct($money)
{
$this->money = $money;
}
/**
* Render the exception into an HTTP response.
*
* @return \Illuminate\Http\Response
*/
public function render()
{
return response()->json([
'message' => trans('coupon::messages.minimum_spend', ['amount' => $this->money->convertToCurrentCurrency()->format()]),
], 403);
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace Modules\Coupon\Http\Controllers\Admin;
use Modules\Coupon\Entities\Coupon;
use Modules\Admin\Traits\HasCrudActions;
use Modules\Coupon\Http\Requests\SaveCouponRequest;
class CouponController
{
use HasCrudActions;
/**
* Model for the resource.
*
* @var string
*/
protected $model = Coupon::class;
/**
* Label of the resource.
*
* @var string
*/
protected $label = 'coupon::coupons.coupon';
/**
* View path of the resource.
*
* @var string
*/
protected $viewPath = 'coupon::admin.coupons';
/**
* Form requests for the resource.
*
* @var array|string
*/
protected $validation = SaveCouponRequest::class;
}

View File

@@ -0,0 +1,67 @@
<?php
namespace Modules\Coupon\Http\Controllers;
use Modules\Cart\Facades\Cart;
use Illuminate\Pipeline\Pipeline;
use Modules\Coupon\Entities\Coupon;
use Modules\Coupon\Checkers\ValidCoupon;
use Modules\Coupon\Checkers\CouponExists;
use Modules\Coupon\Checkers\MaximumSpend;
use Modules\Coupon\Checkers\MinimumSpend;
use Modules\Coupon\Checkers\AlreadyApplied;
use Modules\Coupon\Checkers\ExcludedProducts;
use Modules\Coupon\Checkers\ApplicableProducts;
use Modules\Coupon\Checkers\ExcludedCategories;
use Modules\Coupon\Checkers\UsageLimitPerCoupon;
use Modules\Coupon\Checkers\ApplicableCategories;
use Modules\Coupon\Checkers\UsageLimitPerCustomer;
class CartCouponController
{
private $checkers = [
CouponExists::class,
AlreadyApplied::class,
ValidCoupon::class,
MinimumSpend::class,
MaximumSpend::class,
ApplicableProducts::class,
ExcludedProducts::class,
ApplicableCategories::class,
ExcludedCategories::class,
UsageLimitPerCoupon::class,
UsageLimitPerCustomer::class,
];
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store()
{
$coupon = Coupon::findByCode(request('coupon'));
resolve(Pipeline::class)
->send($coupon)
->through($this->checkers)
->then(function ($coupon) {
Cart::applyCoupon($coupon);
});
return Cart::instance();
}
/**
* Destroy resources by given ids.
*
* @return void
*/
public function destroy()
{
Cart::removeCoupon();
return Cart::instance();
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Modules\Coupon\Http\Requests;
use Modules\Core\Http\Requests\Request;
class SaveCouponRequest extends Request
{
/**
* Available attributes.
*
* @var string
*/
protected $availableAttributes = 'coupon::attributes';
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'required',
'code' => 'required',
'is_percent' => 'required|boolean',
'value' => 'nullable|numeric|min:0|max:99999999999999',
'free_shipping' => 'required|boolean',
'start_date' => 'nullable|date',
'end_date' => 'nullable|date',
'is_active' => 'required|boolean',
'minimum_spend' => 'nullable|numeric|min:0|max:99999999999999',
'maximum_spend' => 'nullable|numeric|min:0|max:99999999999999',
'usage_limit_per_coupon' => 'nullable|numeric|min:0|max:4294967295',
'usage_limit_per_customer' => 'nullable|numeric|min:0|max:4294967295',
];
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Modules\Coupon\Providers;
use Modules\Coupon\Admin\CouponTabs;
use Illuminate\Support\ServiceProvider;
use Modules\Admin\Ui\Facades\TabManager;
class CouponServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* @return void
*/
public function boot()
{
TabManager::register('coupons', CouponTabs::class);
}
}

View File

@@ -0,0 +1,20 @@
<?php
return [
'name' => 'Name',
'code' => 'Code',
'is_percent' => 'Discount Type',
'value' => 'Value',
'free_shipping' => 'Free Shipping',
'start_date' => 'Start date',
'end_date' => 'End date',
'is_active' => 'Status',
'minimum_spend' => 'Minimum Spend',
'maximum_spend' => 'Maximum Spend',
'products' => 'Products',
'exclude_products' => 'Exclude Products',
'categories' => 'Categories',
'exclude_categories' => 'Exclude Categories',
'usage_limit_per_coupon' => 'Usage Limit Per Coupon',
'usage_limit_per_customer' => 'Usage Limit Per Customer',
];

View File

@@ -0,0 +1,27 @@
<?php
return [
'coupon' => 'Coupon',
'coupons' => 'Coupons',
'table' => [
'name' => 'Name',
'code' => 'Code',
'discount' => 'Discount',
],
'tabs' => [
'group' => [
'coupon_information' => 'Coupon Information',
],
'general' => 'General',
'usage_restrictions' => 'Usage Restrictions',
'usage_limits' => 'Usage Limits',
],
'form' => [
'price_types' => [
'0' => 'Fixed',
'1' => 'Percent',
],
'allow_free_shipping' => 'Allow free shipping',
'enable_the_coupon' => 'Enable the coupon',
],
];

View File

@@ -0,0 +1,11 @@
<?php
return [
'not_exists' => 'The coupon does not exist.',
'already_applied' => 'The coupon has been already applied.',
'invalid_coupon' => 'The coupon is not valid.',
'minimum_spend' => 'You need to spend at least :amount to apply this coupon.',
'maximum_spend' => 'You need to spend less than :amount to apply this coupon.',
'inapplicable' => 'This coupon is not applicable to your cart.',
'usage_limit_reached' => 'The coupon usage limit has been reached.',
];

View File

@@ -0,0 +1,8 @@
<?php
return [
'index' => 'Index Coupons',
'create' => 'Create Coupons',
'edit' => 'Edit Coupons',
'destroy' => 'Delete Coupons',
];

View File

@@ -0,0 +1,18 @@
@extends('admin::layout')
@component('admin::components.page.header')
@slot('title', trans('admin::resource.create', ['resource' => trans('coupon::coupons.coupon')]))
<li><a href="{{ route('admin.coupons.index') }}">{{ trans('coupon::coupons.coupons') }}</a></li>
<li class="active">{{ trans('admin::resource.create', ['resource' => trans('coupon::coupons.coupon')]) }}</li>
@endcomponent
@section('content')
<form method="POST" action="{{ route('admin.coupons.store') }}" class="form-horizontal" id="coupon-create-form" novalidate>
{{ csrf_field() }}
{!! $tabs->render(compact('coupon')) !!}
</form>
@endsection
@include('coupon::admin.coupons.partials.scripts')

View File

@@ -0,0 +1,20 @@
@extends('admin::layout')
@component('admin::components.page.header')
@slot('title', trans('admin::resource.edit', ['resource' => trans('coupon::coupons.coupon')]))
@slot('subtitle', $coupon->name)
<li><a href="{{ route('admin.coupons.index') }}">{{ trans('coupon::coupons.coupons') }}</a></li>
<li class="active">{{ trans('admin::resource.edit', ['resource' => trans('coupon::coupons.coupon')]) }}</li>
@endcomponent
@section('content')
<form method="POST" action="{{ route('admin.coupons.update', $coupon) }}" class="form-horizontal" id="coupon-edit-form" novalidate>
{{ csrf_field() }}
{{ method_field('put') }}
{!! $tabs->render(compact('coupon')) !!}
</form>
@endsection
@include('coupon::admin.coupons.partials.scripts')

View File

@@ -0,0 +1,42 @@
@extends('admin::layout')
@component('admin::components.page.header')
@slot('title', trans('coupon::coupons.coupons'))
<li class="active">{{ trans('coupon::coupons.coupons') }}</li>
@endcomponent
@component('admin::components.page.index_table')
@slot('buttons', ['create'])
@slot('resource', 'coupons')
@slot('name', trans('coupon::coupons.coupon'))
@slot('thead')
<tr>
@include('admin::partials.table.select_all')
<th data-sort>{{ trans('admin::admin.table.id') }}</th>
<th>{{ trans('coupon::coupons.table.name') }}</th>
<th>{{ trans('coupon::coupons.table.code') }}</th>
<th>{{ trans('coupon::coupons.table.discount') }}</th>
<th>{{ trans('admin::admin.table.status') }}</th>
<th data-sort>{{ trans('admin::admin.table.created') }}</th>
</tr>
@endslot
@endcomponent
@push('scripts')
<script>
new DataTable('#coupons-table .table', {
columns: [
{ data: 'checkbox', orderable: false, searchable: false, width: '3%' },
{ data: 'id', width: '5%' },
{ data: 'name', name: 'translations.name', orderable: false, defaultContent: '' },
{ data: 'code' },
{ data: 'discount', name: 'value' },
{ data: 'status', name: 'is_active', searchable: false },
{ data: 'created', name: 'created_at' },
],
});
</script>
@endpush

View File

@@ -0,0 +1,16 @@
@include('admin::partials.selectize_remote')
@push('shortcuts')
<dl class="dl-horizontal">
<dt><code>b</code></dt>
<dd>{{ trans('admin::admin.shortcuts.back_to_index', ['name' => trans('coupon::coupons.coupon')]) }}</dd>
</dl>
@endpush
@push('scripts')
<script>
keypressAction([
{ key: 'b', route: "{{ route('admin.coupons.index') }}" },
]);
</script>
@endpush

View File

@@ -0,0 +1,12 @@
<div class="row">
<div class="col-md-8">
{{ Form::text('name', trans('coupon::attributes.name'), $errors, $coupon, ['required' => true]) }}
{{ Form::text('code', trans('coupon::attributes.code'), $errors, $coupon, ['required' => true]) }}
{{ Form::select('is_percent', trans('coupon::attributes.is_percent'), $errors, trans('coupon::coupons.form.price_types'), $coupon) }}
{{ Form::number('value', trans('coupon::attributes.value'), $errors, $coupon, ['min' => 0]) }}
{{ Form::checkbox('free_shipping', trans('coupon::attributes.free_shipping'), trans('coupon::coupons.form.allow_free_shipping'), $errors, $coupon->freeShipping()) }}
{{ Form::text('start_date', trans('coupon::attributes.start_date'), $errors, $coupon, ['class' => 'datetime-picker', 'data-default-date' => $coupon->start_date]) }}
{{ Form::text('end_date', trans('coupon::attributes.end_date'), $errors, $coupon, ['class' => 'datetime-picker', 'data-default-date' => $coupon->end_date]) }}
{{ Form::checkbox('is_active', trans('coupon::attributes.is_active'), trans('coupon::coupons.form.enable_the_coupon'), $errors, $coupon) }}
</div>
</div>

View File

@@ -0,0 +1,6 @@
<div class="row">
<div class="col-md-8">
{{ Form::number('usage_limit_per_coupon', trans('coupon::attributes.usage_limit_per_coupon'), $errors, $coupon) }}
{{ Form::number('usage_limit_per_customer', trans('coupon::attributes.usage_limit_per_customer'), $errors, $coupon) }}
</div>
</div>

View File

@@ -0,0 +1,10 @@
<div class="row">
<div class="col-md-8">
{{ Form::number('minimum_spend', trans('coupon::attributes.minimum_spend'), $errors, $coupon, ['min' => 0]) }}
{{ Form::number('maximum_spend', trans('coupon::attributes.maximum_spend'), $errors, $coupon, ['min' => 0]) }}
{{ Form::select('products', trans('coupon::attributes.products'), $errors, $products, $coupon, ['class' => 'selectize prevent-creation', 'data-url' => route('admin.products.index'), 'multiple' => true]) }}
{{ Form::select('exclude_products', trans('coupon::attributes.exclude_products'), $errors, $excludeProducts, $coupon, ['class' => 'selectize prevent-creation', 'data-url' => route('admin.products.index'), 'multiple' => true]) }}
{{ Form::select('categories', trans('coupon::attributes.categories'), $errors, $categories, $coupon, ['class' => 'selectize prevent-creation', 'multiple' => true]) }}
{{ Form::select('exclude_categories', trans('coupon::attributes.exclude_categories'), $errors, $categories, $coupon, ['class' => 'selectize prevent-creation', 'multiple' => true]) }}
</div>
</div>

View File

@@ -0,0 +1,39 @@
<?php
use Illuminate\Support\Facades\Route;
Route::get('coupons', [
'as' => 'admin.coupons.index',
'uses' => 'CouponController@index',
'middleware' => 'can:admin.coupons.index',
]);
Route::get('coupons/create', [
'as' => 'admin.coupons.create',
'uses' => 'CouponController@create',
'middleware' => 'can:admin.coupons.create',
]);
Route::post('coupons', [
'as' => 'admin.coupons.store',
'uses' => 'CouponController@store',
'middleware' => 'can:admin.coupons.create',
]);
Route::get('coupons/{id}/edit', [
'as' => 'admin.coupons.edit',
'uses' => 'CouponController@edit',
'middleware' => 'can:admin.coupons.edit',
]);
Route::put('coupons/{id}', [
'as' => 'admin.coupons.update',
'uses' => 'CouponController@update',
'middleware' => 'can:admin.coupons.edit',
]);
Route::delete('coupons/{ids?}', [
'as' => 'admin.coupons.destroy',
'uses' => 'CouponController@destroy',
'middleware' => 'can:admin.coupons.destroy',
]);

View File

@@ -0,0 +1,6 @@
<?php
use Illuminate\Support\Facades\Route;
Route::post('cart/coupon', 'CartCouponController@store')->name('cart.coupon.store');
Route::delete('cart/coupon', 'CartCouponController@destroy')->name('cart.coupon.destroy');

View File

@@ -0,0 +1,25 @@
<?php
namespace Modules\Coupon\Sidebar;
use Maatwebsite\Sidebar\Item;
use Maatwebsite\Sidebar\Menu;
use Maatwebsite\Sidebar\Group;
use Modules\Admin\Sidebar\BaseSidebarExtender;
class SidebarExtender extends BaseSidebarExtender
{
public function extend(Menu $menu)
{
$menu->group(trans('admin::sidebar.content'), function (Group $group) {
$group->item(trans('coupon::coupons.coupons'), function (Item $item) {
$item->icon('fa fa-tags');
$item->weight(20);
$item->route('admin.coupons.index');
$item->authorize(
$this->auth->hasAccess('admin.coupons.index')
);
});
});
}
}

View File

@@ -0,0 +1,27 @@
{
"name": "fleetcart/coupon",
"description": "The FleetCart Coupon Module..",
"authors": [
{
"name": "Envay Soft",
"email": "envaysoft@gmail.com"
}
],
"require": {
"php": "^8.0.2"
},
"autoload": {
"psr-4": {
"Modules\\Coupon\\": ""
}
},
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"config": {
"sort-packages": true
},
"minimum-stability": "dev"
}

View File

@@ -0,0 +1,9 @@
{
"name": "Coupon",
"alias": "coupon",
"description": "Coupon management",
"priority": 100,
"providers": [
"Modules\\Coupon\\Providers\\CouponServiceProvider"
]
}