first upload all files
This commit is contained in:
39
Modules/Tax/Admin/TaxTabs.php
Normal file
39
Modules/Tax/Admin/TaxTabs.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Tax\Admin;
|
||||
|
||||
use Modules\Admin\Ui\Tab;
|
||||
use Modules\Admin\Ui\Tabs;
|
||||
use Modules\Support\Country;
|
||||
|
||||
class TaxTabs extends Tabs
|
||||
{
|
||||
public function make()
|
||||
{
|
||||
$this->group('tax_information', trans('tax::taxes.tabs.group.tax_information'))
|
||||
->active()
|
||||
->add($this->general())
|
||||
->add($this->rates());
|
||||
}
|
||||
|
||||
private function general()
|
||||
{
|
||||
return tap(new Tab('general', trans('tax::taxes.tabs.general')), function (Tab $tab) {
|
||||
$tab->active();
|
||||
$tab->weight(5);
|
||||
$tab->fields(['name']);
|
||||
$tab->view('tax::admin.taxes.tabs.general');
|
||||
});
|
||||
}
|
||||
|
||||
private function rates()
|
||||
{
|
||||
return tap(new Tab('rates', trans('tax::taxes.tabs.rates')), function (Tab $tab) {
|
||||
$tab->weight(10);
|
||||
$tab->fields(['name', 'rates.*.name', 'rates.*.rate']);
|
||||
$tab->view('tax::admin.taxes.tabs.rates', [
|
||||
'countries' => Country::supported(),
|
||||
]);
|
||||
});
|
||||
}
|
||||
}
|
||||
22
Modules/Tax/Config/assets.php
Normal file
22
Modules/Tax/Config/assets.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Define which assets will be available through the asset manager
|
||||
|--------------------------------------------------------------------------
|
||||
| These assets are registered on the asset manager
|
||||
*/
|
||||
'all_assets' => [
|
||||
'admin.tax.css' => ['module' => 'tax:admin/css/tax.css'],
|
||||
'admin.tax.js' => ['module' => 'tax:admin/js/tax.js'],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Define which default assets will always be included in your pages
|
||||
| through the asset pipeline
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
'required_assets' => [],
|
||||
];
|
||||
10
Modules/Tax/Config/permissions.php
Normal file
10
Modules/Tax/Config/permissions.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'admin.taxes' => [
|
||||
'index' => 'tax::permissions.taxes.index',
|
||||
'create' => 'tax::permissions.taxes.create',
|
||||
'edit' => 'tax::permissions.taxes.edit',
|
||||
'destroy' => 'tax::permissions.taxes.destroy',
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateTaxClassesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('tax_classes', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->string('based_on');
|
||||
$table->softDeletes();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('tax_classes');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateTaxClassTranslationsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('tax_class_translations', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->integer('tax_class_id')->unsigned();
|
||||
$table->string('locale');
|
||||
$table->string('label');
|
||||
|
||||
$table->unique(['tax_class_id', 'locale']);
|
||||
$table->foreign('tax_class_id')->references('id')->on('tax_classes')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('tax_class_translations');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateTaxRatesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('tax_rates', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->integer('tax_class_id')->unsigned()->index();
|
||||
$table->string('country');
|
||||
$table->string('state');
|
||||
$table->string('city');
|
||||
$table->string('zip');
|
||||
$table->decimal('rate', 8, 4)->unsigned();
|
||||
$table->integer('position')->unsigned();
|
||||
$table->softDeletes();
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('tax_class_id')->references('id')->on('tax_classes')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('tax_rates');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateTaxRateTranslationsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('tax_rate_translations', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->integer('tax_rate_id')->unsigned();
|
||||
$table->string('locale');
|
||||
$table->string('name');
|
||||
|
||||
$table->unique(['tax_rate_id', 'locale']);
|
||||
$table->foreign('tax_rate_id')->references('id')->on('tax_rates')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('tax_rate_translations');
|
||||
}
|
||||
}
|
||||
140
Modules/Tax/Entities/TaxClass.php
Normal file
140
Modules/Tax/Entities/TaxClass.php
Normal file
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Tax\Entities;
|
||||
|
||||
use Modules\Admin\Ui\AdminTable;
|
||||
use Modules\Support\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Modules\Product\Entities\Product;
|
||||
use Modules\Support\Eloquent\Translatable;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class TaxClass extends Model
|
||||
{
|
||||
use Translatable, SoftDeletes;
|
||||
|
||||
const SHIPPING_ADDRESS = 'shipping_address';
|
||||
const BILLING_ADDRESS = 'billing_address';
|
||||
const STORE_ADDRESS = 'store_address';
|
||||
|
||||
/**
|
||||
* The relations to eager load on every query.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $with = ['translations'];
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = ['based_on'];
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public $translatedAttributes = ['label'];
|
||||
|
||||
/**
|
||||
* Perform any actions required after the model boots.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function booted()
|
||||
{
|
||||
static::saved(function ($taxClass) {
|
||||
$taxClass->saveRates(request('rates', []));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tag list.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
public static function list()
|
||||
{
|
||||
return Cache::tags('tax_classes')->rememberForever(md5('tax_classes.list:' . locale()), function () {
|
||||
return self::all()->sortBy('label')->pluck('label', 'id');
|
||||
});
|
||||
}
|
||||
|
||||
public function findTaxRate($addresses)
|
||||
{
|
||||
return $this->taxRates()
|
||||
->findByAddress($this->determineAddress($addresses))
|
||||
->first();
|
||||
}
|
||||
|
||||
public function determineAddress($addresses)
|
||||
{
|
||||
if ($this->based_on === self::SHIPPING_ADDRESS) {
|
||||
return $addresses['shipping'] ?? [];
|
||||
}
|
||||
|
||||
if ($this->based_on === self::BILLING_ADDRESS) {
|
||||
return $addresses['billing'] ?? [];
|
||||
}
|
||||
|
||||
if ($this->based_on === self::STORE_ADDRESS) {
|
||||
return [
|
||||
'address_1' => setting('store_address_1'),
|
||||
'address_2' => setting('store_address_2'),
|
||||
'city' => setting('store_city'),
|
||||
'state' => setting('store_state'),
|
||||
'zip' => setting('store_zip'),
|
||||
'country' => setting('store_country'),
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public function taxRates()
|
||||
{
|
||||
return $this->hasMany(TaxRate::class)->orderBy('position');
|
||||
}
|
||||
|
||||
public function products()
|
||||
{
|
||||
return $this->hasMany(Product::class);
|
||||
}
|
||||
|
||||
public function table()
|
||||
{
|
||||
return new AdminTable($this->newQuery());
|
||||
}
|
||||
|
||||
public function saveRates($rates = [])
|
||||
{
|
||||
$ids = $this->getDeleteCandidates($rates);
|
||||
|
||||
if ($ids->isNotEmpty()) {
|
||||
$this->taxRates()->whereIn('id', $ids)->delete();
|
||||
}
|
||||
|
||||
foreach (array_reset_index($rates) as $index => $rate) {
|
||||
$this->taxRates()->updateOrCreate(
|
||||
['id' => $rate['id']],
|
||||
$rate + ['position' => $index]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function getDeleteCandidates($rates = [])
|
||||
{
|
||||
return $this->taxRates()
|
||||
->pluck('id')
|
||||
->diff(array_pluck($rates, 'id'));
|
||||
}
|
||||
}
|
||||
15
Modules/Tax/Entities/TaxClassTranslation.php
Normal file
15
Modules/Tax/Entities/TaxClassTranslation.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Tax\Entities;
|
||||
|
||||
use Modules\Support\Eloquent\TranslationModel;
|
||||
|
||||
class TaxClassTranslation extends TranslationModel
|
||||
{
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = ['label'];
|
||||
}
|
||||
128
Modules/Tax/Entities/TaxRate.php
Normal file
128
Modules/Tax/Entities/TaxRate.php
Normal file
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Tax\Entities;
|
||||
|
||||
use Modules\Support\Money;
|
||||
use Modules\Order\Entities\Order;
|
||||
use Modules\Support\Eloquent\Model;
|
||||
use Modules\Order\Entities\OrderTax;
|
||||
use Modules\Support\Eloquent\Translatable;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class TaxRate extends Model
|
||||
{
|
||||
use Translatable, SoftDeletes;
|
||||
|
||||
/**
|
||||
* The relations to eager load on every query.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $with = ['translations'];
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = ['country', 'state', 'city', 'zip', 'rate', 'position'];
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public $translatedAttributes = ['name'];
|
||||
|
||||
public function orders()
|
||||
{
|
||||
return $this->belongsToMany(Order::class, 'order_taxes')
|
||||
->using(OrderTax::class)
|
||||
->as('order_tax')
|
||||
->withPivot('amount');
|
||||
}
|
||||
|
||||
public function scopeFindByAddress($query, $address)
|
||||
{
|
||||
$address = $this->mergeDefaultAddress($address);
|
||||
|
||||
$query->whereIn('country', [$address['country'], '*'])
|
||||
->whereIn('state', [$address['state'], '*'])
|
||||
->whereIn('city', [$address['city'], '*'])
|
||||
->whereIn('zip', [$address['zip'], '*'])
|
||||
->orderByRaw($this->rawOrderClause(), [
|
||||
$address['country'],
|
||||
$address['state'],
|
||||
$address['city'],
|
||||
$address['zip'],
|
||||
])
|
||||
->orderBy('position');
|
||||
}
|
||||
|
||||
private function mergeDefaultAddress($address)
|
||||
{
|
||||
return $address += ['country' => null, 'state' => null, 'city' => null, 'zip' => null];
|
||||
}
|
||||
|
||||
private function rawOrderClause()
|
||||
{
|
||||
return "(
|
||||
CASE WHEN country = ? THEN 1 ELSE 0 END +
|
||||
CASE WHEN state = ? THEN 1 ELSE 0 END +
|
||||
CASE WHEN city = ? THEN 1 ELSE 0 END +
|
||||
CASE WHEN zip = ? THEN 1 ELSE 0 END
|
||||
) DESC";
|
||||
}
|
||||
|
||||
public function getCountryAttribute($country)
|
||||
{
|
||||
return $country === '*' ? null : $country;
|
||||
}
|
||||
|
||||
public function setCountryAttribute($country)
|
||||
{
|
||||
$this->attributes['country'] = $country ?: '*';
|
||||
}
|
||||
|
||||
public function getStateAttribute($state)
|
||||
{
|
||||
return $state === '*' ? null : $state;
|
||||
}
|
||||
|
||||
public function setStateAttribute($state)
|
||||
{
|
||||
$this->attributes['state'] = $state ?: '*';
|
||||
}
|
||||
|
||||
public function getCityAttribute($city)
|
||||
{
|
||||
return $city === '*' ? null : $city;
|
||||
}
|
||||
|
||||
public function setCityAttribute($city)
|
||||
{
|
||||
$this->attributes['city'] = $city ?: '*';
|
||||
}
|
||||
|
||||
public function getZipAttribute($zip)
|
||||
{
|
||||
return $zip === '*' ? null : $zip;
|
||||
}
|
||||
|
||||
public function setZipAttribute($zip)
|
||||
{
|
||||
$this->attributes['zip'] = $zip ?: '*';
|
||||
}
|
||||
|
||||
public function getTotalAttribute($total)
|
||||
{
|
||||
return Money::inDefaultCurrency($total);
|
||||
}
|
||||
}
|
||||
15
Modules/Tax/Entities/TaxRateTranslation.php
Normal file
15
Modules/Tax/Entities/TaxRateTranslation.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Tax\Entities;
|
||||
|
||||
use Modules\Support\Eloquent\TranslationModel;
|
||||
|
||||
class TaxRateTranslation extends TranslationModel
|
||||
{
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = ['name'];
|
||||
}
|
||||
54
Modules/Tax/Http/Controllers/Admin/TaxController.php
Normal file
54
Modules/Tax/Http/Controllers/Admin/TaxController.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Tax\Http\Controllers\Admin;
|
||||
|
||||
use Modules\Tax\Entities\TaxClass;
|
||||
use Modules\Admin\Traits\HasCrudActions;
|
||||
use Modules\Tax\Http\Requests\SaveTaxRequest;
|
||||
|
||||
class TaxController
|
||||
{
|
||||
use HasCrudActions;
|
||||
|
||||
/**
|
||||
* Model for the resource.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $model = TaxClass::class;
|
||||
|
||||
/**
|
||||
* The relations to eager load on every query.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $with = ['taxRates'];
|
||||
|
||||
/**
|
||||
* Label of the resource.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $label = 'tax::taxes.tax';
|
||||
|
||||
/**
|
||||
* View path of the resource.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $viewPath = 'tax::admin.taxes';
|
||||
|
||||
/**
|
||||
* Route prefix of the resource.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $routePrefix = 'admin.taxes';
|
||||
|
||||
/**
|
||||
* Form requests for the resource.
|
||||
*
|
||||
* @var array|string
|
||||
*/
|
||||
protected $validation = SaveTaxRequest::class;
|
||||
}
|
||||
31
Modules/Tax/Http/Controllers/CartTaxController.php
Normal file
31
Modules/Tax/Http/Controllers/CartTaxController.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Tax\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Modules\Cart\Facades\Cart;
|
||||
|
||||
class CartTaxController
|
||||
{
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$this->mergeShippingAddress($request);
|
||||
|
||||
Cart::addTaxes($request);
|
||||
|
||||
return Cart::instance();
|
||||
}
|
||||
|
||||
private function mergeShippingAddress($request)
|
||||
{
|
||||
$request->merge([
|
||||
'shipping' => $request->ship_to_a_different_address ? $request->shipping : $request->billing,
|
||||
]);
|
||||
}
|
||||
}
|
||||
57
Modules/Tax/Http/Requests/SaveTaxRequest.php
Normal file
57
Modules/Tax/Http/Requests/SaveTaxRequest.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Tax\Http\Requests;
|
||||
|
||||
use Illuminate\Validation\Rule;
|
||||
use Modules\Tax\Entities\TaxClass;
|
||||
use Modules\Core\Http\Requests\Request;
|
||||
|
||||
class SaveTaxRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Available attributes.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $availableAttributes = 'tax::attributes';
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'label' => 'required',
|
||||
'based_on' => ['required', Rule::in([TaxClass::SHIPPING_ADDRESS, TaxClass::BILLING_ADDRESS, TaxClass::STORE_ADDRESS])],
|
||||
'rates.*.name' => 'required_with:rates.*.rate',
|
||||
'rates.*.rate' => ['required_with:rates.*.name', 'numeric', 'min:0', 'max:9999'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data to be validated from the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function validationData()
|
||||
{
|
||||
return request()->merge([
|
||||
'rates' => $this->filter($this->rates ?? []),
|
||||
])->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter tax rates.
|
||||
*
|
||||
* @param array $rates
|
||||
* @return array
|
||||
*/
|
||||
private function filter($rates = [])
|
||||
{
|
||||
return array_filter($rates, function ($rate) {
|
||||
return ! is_null($rate['name']) || ! is_null($rate['rate']);
|
||||
});
|
||||
}
|
||||
}
|
||||
25
Modules/Tax/Providers/TaxServiceProvider.php
Normal file
25
Modules/Tax/Providers/TaxServiceProvider.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Tax\Providers;
|
||||
|
||||
use Modules\Tax\Admin\TaxTabs;
|
||||
use Modules\Support\Traits\AddsAsset;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Modules\Admin\Ui\Facades\TabManager;
|
||||
|
||||
class TaxServiceProvider extends ServiceProvider
|
||||
{
|
||||
use AddsAsset;
|
||||
|
||||
/**
|
||||
* Bootstrap the application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
TabManager::register('tax_classes', TaxTabs::class);
|
||||
|
||||
$this->addAdminAssets('admin.taxes.(create|edit)', ['admin.tax.css', 'admin.tax.js']);
|
||||
}
|
||||
}
|
||||
80
Modules/Tax/Resources/assets/admin/js/TaxRate.js
Normal file
80
Modules/Tax/Resources/assets/admin/js/TaxRate.js
Normal file
@@ -0,0 +1,80 @@
|
||||
export default class {
|
||||
constructor(rateId, rate = {}) {
|
||||
this.rateId = rateId;
|
||||
this.rate = rate;
|
||||
}
|
||||
|
||||
html() {
|
||||
this.html = this.template({ rateId: this.rateId, rate: this.rate });
|
||||
|
||||
this.eventListeners();
|
||||
|
||||
return this.html;
|
||||
}
|
||||
|
||||
updateState() {
|
||||
this.html.find('.country select').trigger('change');
|
||||
}
|
||||
|
||||
template(data) {
|
||||
let template = _.template($('#tax-rate-template').html());
|
||||
|
||||
return $(template(data));
|
||||
}
|
||||
|
||||
eventListeners(html) {
|
||||
this.html.find('.country select').on('change', (e) => {
|
||||
if (e.currentTarget.value) {
|
||||
this.changeState(e.currentTarget.value);
|
||||
}
|
||||
});
|
||||
|
||||
this.html.on('click', '.delete-row', this.delete);
|
||||
}
|
||||
|
||||
changeState(countryCode) {
|
||||
this.getStateField().prop('disabled', true);
|
||||
|
||||
let oldState = this.getStateField().val();
|
||||
|
||||
$.getJSON(route('countries.states.index', countryCode), (states) => {
|
||||
this.getStateField()
|
||||
.replaceWith(this.getStateTemplate(states))
|
||||
.prop('disabled', false);
|
||||
|
||||
if (oldState) {
|
||||
this.getStateField().val(oldState);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getStateField() {
|
||||
let id = $.escapeSelector(`rates.${this.rateId}.state`);
|
||||
|
||||
return $(`#${id}`);
|
||||
}
|
||||
|
||||
getStateTemplate(states) {
|
||||
if ($.isEmptyObject(states)) {
|
||||
return this.getInputStateTemplate();
|
||||
}
|
||||
|
||||
return this.getSelectStateTemplate(states);
|
||||
}
|
||||
|
||||
getInputStateTemplate() {
|
||||
let template = _.template($('#state-input-template').html());
|
||||
|
||||
return template({ rateId: this.rateId });
|
||||
}
|
||||
|
||||
getSelectStateTemplate(states) {
|
||||
let template = _.template($('#state-select-template').html());
|
||||
|
||||
return template({ rateId: this.rateId, states });
|
||||
}
|
||||
|
||||
delete(e) {
|
||||
$(e.currentTarget).closest('.tax-rate').remove();
|
||||
}
|
||||
}
|
||||
55
Modules/Tax/Resources/assets/admin/js/TaxRates.js
Normal file
55
Modules/Tax/Resources/assets/admin/js/TaxRates.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import TaxRate from "./TaxRate";
|
||||
|
||||
export default class {
|
||||
constructor() {
|
||||
this.rateCount = 0;
|
||||
|
||||
this.addTaxRates(FleetCart.data['tax_rates']);
|
||||
|
||||
if (this.rateCount === 0) {
|
||||
this.addTaxRate();
|
||||
}
|
||||
|
||||
this.addTaxRatesErrors(FleetCart.errors['tax_rates']);
|
||||
|
||||
this.eventListeners();
|
||||
this.sortable();
|
||||
}
|
||||
|
||||
addTaxRates(rates) {
|
||||
for (let rate of rates) {
|
||||
this.addTaxRate(rate)
|
||||
}
|
||||
}
|
||||
|
||||
addTaxRate(rate = {}) {
|
||||
let textRate = new TaxRate(this.rateCount++, rate);
|
||||
|
||||
$('#tax-rates').append(textRate.html());
|
||||
|
||||
textRate.updateState();
|
||||
|
||||
window.admin.tooltip();
|
||||
}
|
||||
|
||||
addTaxRatesErrors(errors) {
|
||||
for (let key in errors) {
|
||||
let id = $.escapeSelector(key);
|
||||
let parent = $(`#${id}`).parent();
|
||||
|
||||
parent.addClass('has-error');
|
||||
parent.append(`<span class="help-block">${errors[key][0]}</span>`);
|
||||
}
|
||||
}
|
||||
|
||||
eventListeners() {
|
||||
$('#add-new-rate').on('click', () => this.addTaxRate());
|
||||
}
|
||||
|
||||
sortable() {
|
||||
Sortable.create(document.getElementById('tax-rates'), {
|
||||
handle: '.drag-icon',
|
||||
animation: 150,
|
||||
});
|
||||
}
|
||||
}
|
||||
5
Modules/Tax/Resources/assets/admin/js/main.js
Normal file
5
Modules/Tax/Resources/assets/admin/js/main.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import TaxRates from './TaxRates';
|
||||
|
||||
window.admin.removeSubmitButtonOffsetOn('#rates');
|
||||
|
||||
new TaxRates();
|
||||
29
Modules/Tax/Resources/assets/admin/sass/main.scss
Normal file
29
Modules/Tax/Resources/assets/admin/sass/main.scss
Normal file
@@ -0,0 +1,29 @@
|
||||
.tax-class {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.tax-rates {
|
||||
.form-group {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.tax-name {
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
.country,
|
||||
.state {
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.country select,
|
||||
.state select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.city,
|
||||
.zip,
|
||||
.rate {
|
||||
min-width: 120px;
|
||||
}
|
||||
}
|
||||
16
Modules/Tax/Resources/lang/en/attributes.php
Normal file
16
Modules/Tax/Resources/lang/en/attributes.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'label' => 'Tax Class',
|
||||
'based_on' => 'Based On',
|
||||
'name' => 'Tax Name',
|
||||
'country' => 'Country',
|
||||
'state' => 'State',
|
||||
'city' => 'City',
|
||||
'zip' => 'Zip',
|
||||
'rate' => 'Rate %',
|
||||
|
||||
// Validation
|
||||
'rates.*.name' => 'Tax Name',
|
||||
'rates.*.rate' => 'Rate',
|
||||
];
|
||||
10
Modules/Tax/Resources/lang/en/permissions.php
Normal file
10
Modules/Tax/Resources/lang/en/permissions.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'taxes' => [
|
||||
'index' => 'Index Tax',
|
||||
'create' => 'Create Tax',
|
||||
'edit' => 'Edit Tax',
|
||||
'destroy' => 'Delete Tax',
|
||||
],
|
||||
];
|
||||
5
Modules/Tax/Resources/lang/en/sidebar.php
Normal file
5
Modules/Tax/Resources/lang/en/sidebar.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'taxes' => 'Taxes',
|
||||
];
|
||||
27
Modules/Tax/Resources/lang/en/taxes.php
Normal file
27
Modules/Tax/Resources/lang/en/taxes.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
use Modules\Tax\Entities\TaxClass;
|
||||
|
||||
return [
|
||||
'tax' => 'Tax',
|
||||
'taxes' => 'Taxes',
|
||||
'table' => [
|
||||
'tax_class' => 'Tax Class',
|
||||
],
|
||||
'tabs' => [
|
||||
'group' => [
|
||||
'tax_information' => 'Tax Information',
|
||||
],
|
||||
'general' => 'General',
|
||||
'rates' => 'Rates',
|
||||
],
|
||||
'form' => [
|
||||
'add_new_rate' => 'Add New Rate',
|
||||
'delete_rate' => 'Delete Rate',
|
||||
'based_on' => [
|
||||
TaxClass::SHIPPING_ADDRESS => 'Shipping Address',
|
||||
TaxClass::BILLING_ADDRESS => 'Billing Address',
|
||||
TaxClass::STORE_ADDRESS => 'Store Address',
|
||||
],
|
||||
],
|
||||
];
|
||||
18
Modules/Tax/Resources/views/admin/taxes/create.blade.php
Normal file
18
Modules/Tax/Resources/views/admin/taxes/create.blade.php
Normal file
@@ -0,0 +1,18 @@
|
||||
@extends('admin::layout')
|
||||
|
||||
@component('admin::components.page.header')
|
||||
@slot('title', trans('admin::resource.create', ['resource' => trans('tax::taxes.tax')]))
|
||||
|
||||
<li><a href="{{ route('admin.taxes.index') }}">{{ trans('tax::taxes.taxes') }}</a></li>
|
||||
<li class="active">{{ trans('admin::resource.create', ['resource' => trans('tax::taxes.tax')]) }}</li>
|
||||
@endcomponent
|
||||
|
||||
@section('content')
|
||||
<form method="POST" action="{{ route('admin.taxes.store') }}" class="form-horizontal" id="tax-create-form" novalidate>
|
||||
{{ csrf_field() }}
|
||||
|
||||
{!! $tabs->render(compact('taxClass')) !!}
|
||||
</form>
|
||||
@endsection
|
||||
|
||||
@include('tax::admin.taxes.partials.shortcuts')
|
||||
20
Modules/Tax/Resources/views/admin/taxes/edit.blade.php
Normal file
20
Modules/Tax/Resources/views/admin/taxes/edit.blade.php
Normal file
@@ -0,0 +1,20 @@
|
||||
@extends('admin::layout')
|
||||
|
||||
@component('admin::components.page.header')
|
||||
@slot('title', trans('admin::resource.edit', ['resource' => trans('tax::taxes.tax')]))
|
||||
@slot('subtitle', $taxClass->title)
|
||||
|
||||
<li><a href="{{ route('admin.taxes.index') }}">{{ trans('tax::taxes.taxes') }}</a></li>
|
||||
<li class="active">{{ trans('admin::resource.edit', ['resource' => trans('tax::taxes.tax')]) }}</li>
|
||||
@endcomponent
|
||||
|
||||
@section('content')
|
||||
<form method="POST" action="{{ route('admin.taxes.update', $taxClass) }}" class="form-horizontal" id="tax-edit-form" novalidate>
|
||||
{{ csrf_field() }}
|
||||
{{ method_field('put') }}
|
||||
|
||||
{!! $tabs->render(compact('taxClass')) !!}
|
||||
</form>
|
||||
@endsection
|
||||
|
||||
@include('tax::admin.taxes.partials.shortcuts')
|
||||
38
Modules/Tax/Resources/views/admin/taxes/index.blade.php
Normal file
38
Modules/Tax/Resources/views/admin/taxes/index.blade.php
Normal file
@@ -0,0 +1,38 @@
|
||||
@extends('admin::layout')
|
||||
|
||||
@component('admin::components.page.header')
|
||||
@slot('title', trans('tax::taxes.taxes'))
|
||||
|
||||
<li class="active">{{ trans('tax::taxes.taxes') }}</li>
|
||||
@endcomponent
|
||||
|
||||
@component('admin::components.page.index_table')
|
||||
@slot('buttons', ['create'])
|
||||
@slot('resource', 'taxes')
|
||||
@slot('name', trans('tax::taxes.tax'))
|
||||
|
||||
@component('admin::components.table')
|
||||
@slot('thead')
|
||||
<tr>
|
||||
@include('admin::partials.table.select_all')
|
||||
|
||||
<th>{{ trans('admin::admin.table.id') }}</th>
|
||||
<th>{{ trans('tax::taxes.table.tax_class') }}</th>
|
||||
<th data-sort>{{ trans('admin::admin.table.created') }}</th>
|
||||
</tr>
|
||||
@endslot
|
||||
@endcomponent
|
||||
@endcomponent
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
new DataTable('#taxes-table .table', {
|
||||
columns: [
|
||||
{ data: 'checkbox', orderable: false, searchable: false, width: '3%' },
|
||||
{ data: 'id', width: '5%' },
|
||||
{ data: 'label', name: 'translations.label', orderable: false, defaultContent: '' },
|
||||
{ data: 'created', name: 'created_at' },
|
||||
],
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
@@ -0,0 +1,14 @@
|
||||
@push('shortcuts')
|
||||
<dl class="dl-horizontal">
|
||||
<dt><code>b</code></dt>
|
||||
<dd>{{ trans('admin::admin.shortcuts.back_to_index', ['name' => trans('tax::taxes.tax')]) }}</dd>
|
||||
</dl>
|
||||
@endpush
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
keypressAction([
|
||||
{ key: 'b', route: "{{ route('admin.taxes.index') }}" }
|
||||
]);
|
||||
</script>
|
||||
@endpush
|
||||
@@ -0,0 +1,6 @@
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
{{ Form::text('label', trans('tax::attributes.label'), $errors, $taxClass, ['required' => true]) }}
|
||||
{{ Form::select('based_on', trans('tax::attributes.based_on'), $errors, trans('tax::taxes.form.based_on'), $taxClass, ['required' => true]) }}
|
||||
</div>
|
||||
</div>
|
||||
37
Modules/Tax/Resources/views/admin/taxes/tabs/rates.blade.php
Normal file
37
Modules/Tax/Resources/views/admin/taxes/tabs/rates.blade.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<div class="tax-rates-wrapper">
|
||||
<div class="table-responsive">
|
||||
<table class="options tax-rates table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>{{ trans('tax::attributes.name') }}</th>
|
||||
<th>{{ trans('tax::attributes.country') }}</th>
|
||||
<th class="state">{{ trans('tax::attributes.state') }}</th>
|
||||
<th class="city">{{ trans('tax::attributes.city') }}</th>
|
||||
<th class="zip">{{ trans('tax::attributes.zip') }}</th>
|
||||
<th class="rate">{{ trans('tax::attributes.rate') }}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody id="tax-rates">
|
||||
{{-- Tax rate row will be added here dynamically using JS --}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-default m-b-15" id="add-new-rate">
|
||||
{{ trans('tax::taxes.form.add_new_rate') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@include('tax::admin.taxes.tabs.templates.rate')
|
||||
@include('tax::admin.taxes.tabs.templates.state_input')
|
||||
@include('tax::admin.taxes.tabs.templates.state_select')
|
||||
|
||||
@push('globals')
|
||||
<script>
|
||||
FleetCart.data['tax_rates'] = {!! old_json('rates', $taxClass->taxRates) !!};
|
||||
FleetCart.errors['tax_rates'] = @json($errors->get('rates.*'), JSON_FORCE_OBJECT);
|
||||
</script>
|
||||
@endpush
|
||||
@@ -0,0 +1,50 @@
|
||||
<script type="text/html" id="tax-rate-template">
|
||||
<tr class="tax-rate">
|
||||
<td class="text-center">
|
||||
<span class="drag-icon">
|
||||
<i class="fa"></i>
|
||||
<i class="fa"></i>
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td class="tax-name">
|
||||
<input type="hidden" name="rates[<%- rateId %>][id]" value="<%- rate.id %>">
|
||||
|
||||
<input type="text" name="rates[<%- rateId %>][name]" value="<%- rate.name %>" class="form-control" id="rates.<%- rateId %>.name">
|
||||
</td>
|
||||
|
||||
<td class="country">
|
||||
<select class="custom-select-black" name="rates[<%- rateId %>][country]" id="rates.<%- rateId %>.country">
|
||||
<option value="">{{ trans('admin::admin.form.please_select') }}</option>
|
||||
|
||||
@foreach ($countries as $code => $name)
|
||||
<option value="{{ $code }}" <%= rate.country === '{{ $code }}' ? 'selected' : '' %>>
|
||||
{{ $name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</td>
|
||||
|
||||
<td class="state">
|
||||
<input type="text" name="rates[<%- rateId %>][state]" value="<%- rate.state %>" class="form-control" id="rates.<%- rateId %>.state" placeholder="*">
|
||||
</td>
|
||||
|
||||
<td class="city">
|
||||
<input type="text" name="rates[<%- rateId %>][city]" value="<%- rate.city %>" class="form-control" id="rates.<%- rateId %>.city" placeholder="*">
|
||||
</td>
|
||||
|
||||
<td class="zip">
|
||||
<input type="text" name="rates[<%- rateId %>][zip]" value="<%- rate.zip %>" class="form-control" id="rates.<%- rateId %>.zip" placeholder="*">
|
||||
</td>
|
||||
|
||||
<td class="rate">
|
||||
<input type="number" name="rates[<%- rateId %>][rate]" value="<%- rate.rate %>" step="0.01" min="0" class="form-control" id="rates.<%- rateId %>.rate">
|
||||
</td>
|
||||
|
||||
<td class="text-center">
|
||||
<button type="button" class="btn btn-default delete-row" data-toggle="tooltip" title="{{ trans('tax::taxes.form.delete_rate') }}">
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<script type="text/html" id="state-input-template">
|
||||
<input type="text" name="rates[<%- rateId %>][state]" class="form-control" id="rates.<%- rateId %>.state" placeholder="*">
|
||||
</script>
|
||||
@@ -0,0 +1,9 @@
|
||||
<script type="text/html" id="state-select-template">
|
||||
<select name="rates[<%- rateId %>][state]" class="form-control custom-select-black" id="rates.<%- rateId %>.state">
|
||||
<option value="">{{ trans('admin::admin.form.please_select') }}</option>
|
||||
|
||||
<% _.forEach(states, function (state, code) { %>
|
||||
<option value="<%- code %>"><%- state %></option>
|
||||
<% }); %>
|
||||
</select>
|
||||
</script>
|
||||
39
Modules/Tax/Routes/admin.php
Normal file
39
Modules/Tax/Routes/admin.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::get('taxes', [
|
||||
'as' => 'admin.taxes.index',
|
||||
'uses' => 'TaxController@index',
|
||||
'middleware' => 'can:admin.taxes.index',
|
||||
]);
|
||||
|
||||
Route::get('taxes/create', [
|
||||
'as' => 'admin.taxes.create',
|
||||
'uses' => 'TaxController@create',
|
||||
'middleware' => 'can:admin.taxes.create',
|
||||
]);
|
||||
|
||||
Route::post('taxes', [
|
||||
'as' => 'admin.taxes.store',
|
||||
'uses' => 'TaxController@store',
|
||||
'middleware' => 'can:admin.taxes.create',
|
||||
]);
|
||||
|
||||
Route::get('taxes/{id}/edit', [
|
||||
'as' => 'admin.taxes.edit',
|
||||
'uses' => 'TaxController@edit',
|
||||
'middleware' => 'can:admin.taxes.edit',
|
||||
]);
|
||||
|
||||
Route::put('taxes/{id}', [
|
||||
'as' => 'admin.taxes.update',
|
||||
'uses' => 'TaxController@update',
|
||||
'middleware' => 'can:admin.taxes.edit',
|
||||
]);
|
||||
|
||||
Route::delete('taxes/{ids?}', [
|
||||
'as' => 'admin.taxes.destroy',
|
||||
'uses' => 'TaxController@destroy',
|
||||
'middleware' => 'can:admin.taxes.destroy',
|
||||
]);
|
||||
5
Modules/Tax/Routes/public.php
Normal file
5
Modules/Tax/Routes/public.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::post('cart/taxes', 'CartTaxController@store')->name('cart.taxes.store');
|
||||
26
Modules/Tax/Sidebar/SidebarExtender.php
Normal file
26
Modules/Tax/Sidebar/SidebarExtender.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Tax\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.system'), function (Group $group) {
|
||||
$group->item(trans('admin::sidebar.localization'), function (Item $item) {
|
||||
$item->item(trans('tax::sidebar.taxes'), function (Item $item) {
|
||||
$item->weight(15);
|
||||
$item->route('admin.taxes.index');
|
||||
$item->authorize(
|
||||
$this->auth->hasAccess('admin.taxes.index')
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
27
Modules/Tax/composer.json
Normal file
27
Modules/Tax/composer.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "fleetcart/tax",
|
||||
"description": "The FleetCart Tax Module.",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Envay Soft",
|
||||
"email": "envaysoft@gmail.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^8.0.2"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Modules\\Tax\\": ""
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"minimum-stability": "dev"
|
||||
}
|
||||
9
Modules/Tax/module.json
Normal file
9
Modules/Tax/module.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "Tax",
|
||||
"alias": "tax",
|
||||
"description": "The FleetCart Tax Module.",
|
||||
"priority": 100,
|
||||
"providers": [
|
||||
"Modules\\Tax\\Providers\\TaxServiceProvider"
|
||||
]
|
||||
}
|
||||
8
Modules/Tax/webpack.mix.js
Normal file
8
Modules/Tax/webpack.mix.js
Normal file
@@ -0,0 +1,8 @@
|
||||
let mix = require('laravel-mix');
|
||||
let execSync = require('child_process').execSync;
|
||||
|
||||
mix.js(`${__dirname}/Resources/assets/admin/js/main.js`, `${__dirname}/Assets/admin/js/tax.js`)
|
||||
.sass(`${__dirname}/Resources/assets/admin/sass/main.scss`, `${__dirname}/Assets/admin/css/tax.css`)
|
||||
.then(() => {
|
||||
execSync(`npm run rtlcss ${__dirname}/Assets/admin/css/tax.css ${__dirname}/Assets/admin/css/tax.rtl.css`);
|
||||
});
|
||||
Reference in New Issue
Block a user