add: full multi-tenancy control

This commit is contained in:
Cauê Faleiros
2026-02-02 15:31:15 -03:00
commit c6ec92802b
1711 changed files with 258106 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
<?php
namespace Webkul\Quote\Contracts;
interface Quote {}

View File

@@ -0,0 +1,5 @@
<?php
namespace Webkul\Quote\Contracts;
interface QuoteItem {}

View File

@@ -0,0 +1,52 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('quotes', function (Blueprint $table) {
$table->increments('id');
$table->string('subject');
$table->string('description')->nullable();
$table->json('billing_address')->nullable();
$table->json('shipping_address')->nullable();
$table->decimal('discount_percent', 12, 4)->default(0)->nullable();
$table->decimal('discount_amount', 12, 4)->nullable();
$table->decimal('tax_amount', 12, 4)->nullable();
$table->decimal('adjustment_amount', 12, 4)->nullable();
$table->decimal('sub_total', 12, 4)->nullable();
$table->decimal('grand_total', 12, 4)->nullable();
$table->datetime('expired_at')->nullable();
$table->integer('person_id')->unsigned();
$table->foreign('person_id')->references('id')->on('persons')->onDelete('cascade');
$table->integer('user_id')->unsigned();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('quotes');
}
};

View File

@@ -0,0 +1,49 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('quote_items', function (Blueprint $table) {
$table->increments('id');
$table->string('sku')->nullable();
$table->string('name')->nullable();
$table->integer('quantity')->default(0)->nullable();
$table->decimal('price', 12, 4)->default(0);
$table->string('coupon_code')->nullable();
$table->decimal('discount_percent', 12, 4)->default(0)->nullable();
$table->decimal('discount_amount', 12, 4)->default(0)->nullable();
$table->decimal('tax_percent', 12, 4)->default(0)->nullable();
$table->decimal('tax_amount', 12, 4)->default(0)->nullable();
$table->decimal('total', 12, 4)->default(0);
$table->integer('product_id')->unsigned();
$table->integer('quote_id')->unsigned();
$table->foreign('quote_id')->references('id')->on('quotes')->onDelete('cascade');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('quote_items');
}
};

View File

@@ -0,0 +1,76 @@
<?php
namespace Webkul\Quote\Models;
use Illuminate\Database\Eloquent\Model;
use Webkul\Attribute\Traits\CustomAttribute;
use Webkul\Contact\Models\PersonProxy;
use Webkul\Lead\Models\LeadProxy;
use Webkul\Quote\Contracts\Quote as QuoteContract;
use Webkul\User\Models\UserProxy;
class Quote extends Model implements QuoteContract
{
use CustomAttribute;
protected $table = 'quotes';
protected $casts = [
'billing_address' => 'array',
'shipping_address' => 'array',
'expired_at' => 'datetime',
];
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'subject',
'description',
'billing_address',
'shipping_address',
'discount_percent',
'discount_amount',
'tax_amount',
'adjustment_amount',
'sub_total',
'grand_total',
'expired_at',
'user_id',
'person_id',
];
/**
* Get the quote items record associated with the quote.
*/
public function items()
{
return $this->hasMany(QuoteItemProxy::modelClass());
}
/**
* Get the user that owns the quote.
*/
public function user()
{
return $this->belongsTo(UserProxy::modelClass());
}
/**
* Get the person that owns the quote.
*/
public function person()
{
return $this->belongsTo(PersonProxy::modelClass());
}
/**
* The leads that belong to the quote.
*/
public function leads()
{
return $this->belongsToMany(LeadProxy::modelClass(), 'lead_quotes');
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Webkul\Quote\Models;
use Illuminate\Database\Eloquent\Model;
use Webkul\Quote\Contracts\QuoteItem as QuoteItemContract;
class QuoteItem extends Model implements QuoteItemContract
{
protected $table = 'quote_items';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'sku',
'name',
'quantity',
'price',
'coupon_code',
'discount_percent',
'discount_amount',
'tax_percent',
'tax_amount',
'total',
'product_id',
'quote_id',
];
/**
* Get the quote record associated with the quote item.
*/
public function quote()
{
return $this->belongsTo(QuoteProxy::modelClass());
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Webkul\Quote\Models;
use Konekt\Concord\Proxies\ModelProxy;
class QuoteItemProxy extends ModelProxy {}

View File

@@ -0,0 +1,7 @@
<?php
namespace Webkul\Quote\Models;
use Konekt\Concord\Proxies\ModelProxy;
class QuoteProxy extends ModelProxy {}

View File

@@ -0,0 +1,13 @@
<?php
namespace Webkul\Quote\Providers;
use Webkul\Core\Providers\BaseModuleServiceProvider;
class ModuleServiceProvider extends BaseModuleServiceProvider
{
protected $models = [
\Webkul\Quote\Models\Quote::class,
\Webkul\Quote\Models\QuoteItem::class,
];
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Webkul\Quote\Providers;
use Illuminate\Routing\Router;
use Illuminate\Support\ServiceProvider;
class QuoteServiceProvider extends ServiceProvider
{
/**
* Bootstrap services.
*
* @return void
*/
public function boot(Router $router)
{
$this->loadMigrationsFrom(__DIR__.'/../Database/Migrations');
}
/**
* Register services.
*
* @return void
*/
public function register() {}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace Webkul\Quote\Repositories;
use Illuminate\Container\Container;
use Webkul\Core\Eloquent\Repository;
use Webkul\Product\Repositories\ProductRepository;
class QuoteItemRepository extends Repository
{
/**
* Create a new repository instance.
*
* @return void
*/
public function __construct(
protected ProductRepository $productRepository,
Container $container
) {
parent::__construct($container);
}
/**
* Specify Model class name
*
* @return mixed
*/
public function model()
{
return 'Webkul\Quote\Contracts\QuoteItem';
}
/**
* @return mixed
*/
public function create(array $data)
{
if (empty($data['product_id'])) {
return null;
}
$product = $this->productRepository->findOrFail($data['product_id']);
$quoteItem = parent::create(array_merge($data, [
'sku' => $product->sku,
'name' => $product->name,
]));
return $quoteItem;
}
/**
* @param int $id
* @param string $attribute
* @return \Webkul\Quote\Contracts\QuoteItem
*/
public function update(array $data, $id, $attribute = 'id')
{
$product = $this->productRepository->findOrFail($data['product_id']);
$quoteItem = parent::update(array_merge($data, [
'sku' => $product->sku,
'name' => $product->name,
]), $id);
return $quoteItem;
}
}

View File

@@ -0,0 +1,147 @@
<?php
namespace Webkul\Quote\Repositories;
use Illuminate\Container\Container;
use Illuminate\Support\Str;
use Webkul\Attribute\Repositories\AttributeRepository;
use Webkul\Attribute\Repositories\AttributeValueRepository;
use Webkul\Core\Eloquent\Repository;
use Webkul\Quote\Contracts\Quote;
class QuoteRepository extends Repository
{
/**
* Searchable fields.
*/
protected $fieldSearchable = [
'subject',
'description',
'person_id',
'person.name',
'user_id',
'user.name',
];
/**
* Create a new repository instance.
*
* @return void
*/
public function __construct(
protected AttributeRepository $attributeRepository,
protected AttributeValueRepository $attributeValueRepository,
protected QuoteItemRepository $quoteItemRepository,
Container $container
) {
parent::__construct($container);
}
/**
* Specify model class name.
*
* @return mixed
*/
public function model()
{
return Quote::class;
}
/**
* Create.
*
* @return \Webkul\Quote\Contracts\Quote
*/
public function create(array $data)
{
$quote = parent::create($data);
$this->attributeValueRepository->save(array_merge($data, [
'entity_id' => $quote->id,
]));
foreach ($data['items'] as $itemData) {
$this->quoteItemRepository->create(array_merge($itemData, [
'quote_id' => $quote->id,
]));
}
return $quote;
}
/**
* Update.
*
* @param int $id
* @param array $attribute
* @return \Webkul\Quote\Contracts\Quote
*/
public function update(array $data, $id, $attributes = [])
{
$quote = $this->find($id);
parent::update($data, $id);
/**
* If attributes are provided then only save the provided attributes and return.
*/
if (! empty($attributes)) {
$conditions = ['entity_type' => $data['entity_type']];
if (isset($data['quick_add'])) {
$conditions['quick_add'] = 1;
}
$attributes = $this->attributeRepository->where($conditions)
->whereIn('code', $attributes)
->get();
$this->attributeValueRepository->save(array_merge($data, [
'entity_id' => $quote->id,
]), $attributes);
return $quote;
}
$this->attributeValueRepository->save(array_merge($data, [
'entity_id' => $quote->id,
]));
$previousItemIds = $quote->items->pluck('id');
if (isset($data['items'])) {
foreach ($data['items'] as $itemId => $itemData) {
if (Str::contains($itemId, 'item_')) {
$this->quoteItemRepository->create(array_merge($itemData, [
'quote_id' => $id,
]));
} else {
if (is_numeric($index = $previousItemIds->search($itemId))) {
$previousItemIds->forget($index);
}
$this->quoteItemRepository->update($itemData, $itemId);
}
}
}
foreach ($previousItemIds as $itemId) {
$this->quoteItemRepository->delete($itemId);
}
return $quote;
}
/**
* Retrieves customers count based on date.
*
* @return number
*/
public function getQuotesCount($startDate, $endDate)
{
return $this
->whereBetween('created_at', [$startDate, $endDate])
->get()
->count();
}
}