Offres Spéciales :

commit 2bdbfaec541c3d51489f32e0fa6f7523ca4e4f6f
Author: Olivier Legat <olivier.legat@ldc.fr>
Date:   Thu Apr 8 15:48:13 2021 +0200

    arrondis de taxe dans le panier

commit ce748bcac15a0b633cacc06b8232cbb92a8e455c
Author: Olivier Legat <olivier.legat@ldc.fr>
Date:   Thu Apr 8 11:53:54 2021 +0200

    Prix calcules - affichés

commit cc79e7c633d81290371eceabe9864d4d0ee9e4a5
Author: Olivier Legat <olivier.legat@ldc.fr>
Date:   Wed Apr 7 18:09:12 2021 +0200

    Affichage des promowork in progress => prix remisés

commit 8545d1079da2456f090557eeef005e4d37154a1e
Author: Olivier Legat <olivier.legat@ldc.fr>
Date:   Wed Apr 7 13:04:28 2021 +0200

    resource BO Promotions

commit d7145835de675da32e57bec3f0b8f06e1e447b27
Author: Olivier Legat <olivier.legat@ldc.fr>
Date:   Tue Apr 6 18:23:36 2021 +0200

    structure DB, debut BO
This commit is contained in:
Olivier Legat 2021-04-08 15:49:20 +02:00
parent 293cb74391
commit bce4c2f88c
40 changed files with 2586 additions and 181 deletions

View file

@ -39,7 +39,11 @@ class BasketController extends Controller
$dbpanier = new Collection();
foreach (Cart::content() as $row) {
$product = $row->model;
$product->additionnalProductInfo = $product->additionnalProductInfo($product->product_type);
//$product->additionnalProductInfo = $product->additionnalProductInfo($product->product_type);
$product->calculatedPrices = $product->getCalcPrices();
$row->setTaxRate($product->tax->rate);
$row->setDiscountRate($product->discountRate);
$cartextendedItem = array(
'cartItem' => $row,

View file

@ -101,14 +101,23 @@ class OrdersController extends Controller
$order_line->order_id = $order->id;
$order_line->product_id = $cartItem->model->id;
$order_line->quantity = $cartItem->qty;
$order_line->unit_price = $cartItem->price;
$order_line->unit_discount = 0;
//gross unit price
$order_line->unit_price = $cartItem->model->priceHt;
//discount per unit
$order_line->unit_discount = $cartItem->model->discountAmount;
//tax rate for this line
$order_line->tax_rate = $cartItem->taxRate;
$order_line->unit_tax = ($order_line->unit_price * ($order_line->tax_rate / 100));
//tax amount par unit
$order_line->unit_tax = (($order_line->unit_price - $order_line->unit_discount) * ($order_line->tax_rate / 100));
//discount amount for the line
$order_line->total_discount = $order_line->unit_discount * $order_line->quantity;
$order_line->total_ht = $order_line->unit_price * $order_line->quantity;
//gross amount for the line
$order_line->total_ht = ($order_line->unit_price - $order_line->unit_discount) * $order_line->quantity;
//tax amount for the line
$order_line->total_tax = $order_line->unit_tax * $order_line->quantity;
//total TTC avec remises de la ligne
$order_line->total_ttc = $order_line->total_ht + $order_line->total_tax;
$order_line->save();
}

View file

@ -14,7 +14,7 @@ class ProductsController extends Controller
{
public function index()
{
$products = Product::all();
$products = Product::paginate(10);
return view('bo.products.index', compact('products'));
}

View file

@ -0,0 +1,136 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Models\DiscountType;
use App\Models\Promotion;
use Illuminate\Http\Request;
class PromotionsController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$promotions = Promotion::all();
return view('bo.promotions.index', compact('promotions'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
$discTypes = DiscountType::all();
return view('bo.promotions.create', compact('discTypes'));
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$productIds = explode(",", $request->product_id);
$discType = $request->discount_type_id;
$value = $request->discount_value;
$date_start = $request->date_start_date . " " . $request->date_start_time;
$date_end = $request->date_end_date . " " . $request->date_end_time;
$request->validate([]);
$promotion = new Promotion();
$promotion->discount_type_id = $discType;
$promotion->discount_value = $value;
$promotion->date_start = $date_start;
$promotion->date_end = $date_end;
$promotion->save();
//prise en charge de la liaison avec les produits
$promotion->products()->sync($productIds);
return redirect()->route('bo.promotions.index');
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
$promotion = Promotion::findOrFail($id);
$discTypes = DiscountType::all();
return view('bo.promotions.edit', compact('promotion', 'discTypes'));
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
$promotion = Promotion::findOrFail($id);
$productIds = explode(",", $request->product_id);
$discType = $request->discount_type_id;
$value = $request->discount_value;
$date_start = $request->date_start_date . " " . $request->date_start_time;
$date_end = $request->date_end_date . " " . $request->date_end_time;
$request->validate([]);
$promotion->discount_type_id = $discType;
$promotion->discount_value = $value;
$promotion->date_start = $date_start;
$promotion->date_end = $date_end;
$promotion->save();
//prise en charge de la liaison avec les produits
$promotion->products()->sync($productIds);
return redirect()->route('bo.promotions.edit', $promotion->id)->with('success', 'Modifications enregistrés !');
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
$p = Promotion::findOrFail($id);
//prise en charge de la liaison avec les produits
$p->products()->detach();
$p->delete();
return redirect()->route('bo.promotions.index');
}
}

View file

@ -20,7 +20,9 @@ class ProductsController extends Controller
{
$range = Range::where('slug', $range_slug)->first();
$products = Product::where('range_id', $range->id)->paginate(9);
return view('range', compact('range','products'));
//find promo within range
$offres = Product::where('range_id', $range->id)->has('promotions')->with('promotions')->get();
return view('range', compact('range','products','offres'));
}
public function product(Request $request, $product_slug) {

View file

@ -0,0 +1,42 @@
<?php
namespace App\Lib\LDC;
use Gloudemans\Shoppingcart\CartItem;
use Gloudemans\Shoppingcart\Contracts\Calculator;
class CartCalculator implements Calculator
{
public static function getAttribute(string $attribute, CartItem $cartItem)
{
$decimals = config('cart.format.decimals', 2);
switch ($attribute) {
case 'discount':
return $cartItem->price * ($cartItem->getDiscountRate() / 100);
case 'tax':
return round($cartItem->priceTarget * ($cartItem->taxRate / 100), $decimals);
case 'priceTax':
return round($cartItem->priceTarget + $cartItem->tax, $decimals);
case 'discountTotal':
return round($cartItem->discount * $cartItem->qty, $decimals);
case 'priceTotal':
return round($cartItem->price * $cartItem->qty, $decimals);
case 'subtotal':
return max(round($cartItem->priceTotal - $cartItem->discountTotal, $decimals), 0);
case 'priceTarget':
return round(($cartItem->priceTotal - $cartItem->discountTotal) / $cartItem->qty, $decimals);
case 'taxTotal':
//taxe pour un item multiplié par la quantité
$unit_price = max($cartItem->price - $cartItem->discount,0);
$unit_tax = round($unit_price * ($cartItem->taxRate / 100), $decimals);
$taxTotal = round($unit_tax * $cartItem->qty, $decimals);
//return round($cartItem->subtotal * ($cartItem->taxRate / 100), $decimals);
return $taxTotal;
case 'total':
return round($cartItem->subtotal + $cartItem->taxTotal, $decimals);
default:
return;
}
}
}

View file

@ -0,0 +1,17 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class DiscountType extends Model
{
use HasFactory;
public $timestamps = false;
public function promotions() {
return $this->hasMany(Promotion::class, 'discount_type_id');
}
}

View file

@ -16,6 +16,7 @@ class Order extends Model
return $this->belongsToMany(Product::class, 'order_lines', 'order_id', 'product_id');
}
//Relations
public function lines()
{
return $this->hasMany(OrderLine::class);
@ -33,4 +34,10 @@ class Order extends Model
{
return $this->hasOne(Timeslot::class);
}
//Formattages
public function getFormattedTotalTtcAttribute()
{
return $this->attributes['formattedTotalTtc'] = sprintf('%s €', number_format($this->attributes['total'], 2, ',', ' '));
}
}

View file

@ -9,6 +9,7 @@ class OrderLine extends Model
{
use HasFactory;
//Relations
public function order()
{
return $this->belongsTo(Order::class);
@ -18,4 +19,10 @@ class OrderLine extends Model
{
return $this->belongsTo(Product::class);
}
//Formattages
public function getFormattedTotalTtcAttribute()
{
return $this->attributes['formattedTotalTtc'] = sprintf('%s €', number_format($this->attributes['total_ttc'], 2, ',', ' '));
}
}

View file

@ -2,6 +2,7 @@
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
@ -14,18 +15,200 @@ class Product extends Model implements Buyable
use SoftDeletes;
use CanBeBought;
//special formatted attribute price
public function getFormattedPriceAttribute()
/**
* Store in protected variables instead of attributes
* Because those are not set in attributes, Laravel will not try to save it to database
*/
protected $priceHt;
protected $discountedPriceHt;
protected $priceTtc;
protected $discountedPriceTtc;
protected $discountAmount;
protected $discountRate;
/**
* init prices on boot
*/
protected static function boot()
{
return $this->attributes['formattedPrice'] = sprintf('%s €', number_format($this->attributes['price'], 2, ',', ' '));
parent::boot();
//on data retrived from DB
static::retrieved(function ($model) {
$model->priceHt = $model->getPriceHtAttribute();
$model->priceTtc = $model->getPriceTtcAttribute();
$model->discountAmount = $model->getDiscountAmountAttribute();
$model->discountRate = $model->getDiscountRateAttribute();
$model->discountedPriceHt = $model->getDiscountedPriceHtAttribute();
$model->discountedPriceTtc = $model->getDiscountedPriceTtcAttribute();
});
}
public function getFormattedPriceTTCAttribute()
/**
* Price calculations
* We need : gross price (prix avant taxe et remises)
* discounted price (prix avant taxe avec remises)
* net price (prix avec taxe)
* net discounted proice (prix avec taxe et remises)
*/
//Utility to bring prices as a pseudo relation
public function getCalcPrices()
{
$ttc = $this->attributes['price'] * 1.2;
return $this->attributes['formattedPriceTTC'] = sprintf('%s €', number_format($ttc, 2, ',', ' '));
//$prices = new Collection();
$prices = array(
'priceHt' => $this->priceHt,
'discountAmount' => $this->discountAmount,
'discountRate' => $this->discountRate,
'discountedPriceHt' => $this->discountedPriceHt,
'priceTtc' => $this->priceTtc,
'discountedPriceTtc' => $this->discountedPriceTtc,
);
//$prices->add($cartextendedItem);
return $prices;
}
//gross price
/**
* Mutator method to set priceHt's value
*/
public function setPriceHtAttribute($value)
{
$this->priceHt = $value;
}
/**
* Get the gross price
* Accessor method for priceHt
*
* @author Olivier Legat <olivier.legat@ldc.fr>
*/
public function getPriceHtAttribute()
{
if(isset($this->priceHt)) {
return $this->priceHt;
} else {
//no calculations, alias of db property
return $this->priceHt = $this->attributes['price'];
}
}
//discounted price
public function setDiscountedPriceHtAttribute($value)
{
$this->discountedPriceHt = $value;
}
public function getDiscountedPriceHtAttribute()
{
if (isset($this->discountedPriceHt)) {
return $this->discountedPriceHt;
} else {
return $this->discountedPriceHt = $this->priceHt - $this->discountAmount;
}
}
//net price
public function setPriceTtcAttribute($value)
{
$this->priceTtc = $value;
}
public function getPriceTtcAttribute()
{
if (isset($this->priceTtc)) {
return $this->priceTtc;
} else {
return $this->priceTtc = $this->priceHt * ( 1 + ( $this->tax->rate / 100));
}
}
//net discounted price
public function setDiscountedPriceTtcAttribute($value)
{
$this->discountedPriceTtc = $value;
}
public function getDiscountedPriceTtcAttribute()
{
if (isset($this->discountedPriceTtc)) {
return $this->discountedPriceTtc;
} else {
return $this->discountedPriceTtc = $this->discountedPriceHt * ( 1 + ( $this->tax->rate / 100));
}
}
/**
* Discount calc
*/
//montant de remise
public function setDiscountAmountAttribute($value)
{
$this->discountAmount = $value;
}
public function getDiscountAmountAttribute()
{
if (isset($this->discountAmount)) {
return $this->discountAmount;
} else {
$applicablePromotion = $this->promotions->first();
if (null !== $applicablePromotion) {
$promo_type = $applicablePromotion->discount_type_id;
if ($promo_type == 1) {
$discountAmount = $applicablePromotion->discount_value;
} else {
$discountAmount = $this->priceHt * ($applicablePromotion->discount_value / 100);
}
} else {
$discountAmount = 0;
}
return $this->discountAmount = $discountAmount;
}
}
//taux de remise
public function setDiscountRateAttribute($value)
{
$this->discountRate = $value;
}
public function getDiscountRateAttribute()
{
if (isset($this->discountRate)) {
return $this->discountRate;
} else {
$applicablePromotion = $this->promotions->first();
if (null !== $applicablePromotion) {
$promo_type = $applicablePromotion->discount_type_id;
if ($promo_type == 1) {
//on veut un pourcentage
$discountRate = ($applicablePromotion->discount_value / $this->priceHt) * 100;
} else {
$discountRate = $applicablePromotion->discount_value;
}
} else {
$discountRate = 0;
}
return $this->discountRate = $discountRate;
}
}
// //Buyable interface
// public function getBuyablePrice($options = null)
// {
// return $this->discountedPriceHt;
// }
//Relations
public function range()
{
return $this->belongsTo('App\Models\Range', 'range_id');
@ -62,8 +245,23 @@ class Product extends Model implements Buyable
//relationship to associated products
public function associated_products()
public function associatedProducts()
{
return $this->belongsToMany(Product::class, 'products_products', 'product_id', 'associated_id');
}
//offres speciales
public function promotions()
{
$now = Carbon::now();
return $this->belongsToMany(Promotion::class)
->whereDate('date_start', '<=', $now)
->whereDate('date_end', '>=', $now);
}
//taxe
public function tax()
{
return $this->belongsTo(TaxRate::class, 'tax_rate_id');
}
}

31
app/Models/Promotion.php Normal file
View file

@ -0,0 +1,31 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Promotion extends Model
{
use HasFactory;
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'date_start' => 'datetime',
'date_end' => 'datetime',
];
public function products()
{
return $this->belongsToMany(Product::class);
}
public function discountType()
{
return $this->belongsTo(DiscountType::class, 'discount_type_id');
}
}

17
app/Models/TaxRate.php Normal file
View file

@ -0,0 +1,17 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class TaxRate extends Model
{
use HasFactory;
public $timestamps = false;
public function products() {
return $this->hasMany(Product::class, 'tax_rate_id');
}
}

View file

@ -5,6 +5,7 @@ namespace App\Providers;
use App\Models\Range;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Blade;
class AppServiceProvider extends ServiceProvider
{
@ -32,5 +33,10 @@ class AppServiceProvider extends ServiceProvider
// on partage les ranges (gammes produit)
$ranges = Range::all();
View::share('ranges', $ranges);
Blade::directive('fmt_currency', function ($expression) {
/* return "<?php echo sprintf('%s €', number_format($expression, 2, ',', ' ')); ?>"; */
return "<?php echo sprintf('%s €', $expression); ?>";
});
}
}

View file

@ -12,7 +12,8 @@ return [
|
*/
'calculator' => \Gloudemans\Shoppingcart\Calculation\DefaultCalculator::class,
//'calculator' => \Gloudemans\Shoppingcart\Calculation\DefaultCalculator::class,
'calculator' => \App\Lib\LDC\CartCalculator::class,
/*
|--------------------------------------------------------------------------
@ -70,9 +71,9 @@ return [
'decimals' => 2,
'decimal_point' => ',',
'decimal_point' => '.',
'thousand_separator' => ' ',
'thousand_separator' => '',
],

View file

@ -0,0 +1,35 @@
<?php
use Database\Seeders\DiscountTypesSeeder;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateDiscountTypesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('discount_types', function (Blueprint $table) {
$table->id();
$table->string('label');
});
$seeder = new DiscountTypesSeeder();
$seeder->run();
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('discount_types');
}
}

View file

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePromotionsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('promotions', function (Blueprint $table) {
$table->id();
$table->foreignId('discount_type_id')->default(1)->constrained('discount_types');
$table->float('discount_value')->default(0);
$table->timestamp('date_start')->nullable();
$table->timestamp('date_end')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('promotions');
}
}

View file

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateProductPromotionTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('product_promotion', function (Blueprint $table) {
$table->foreignId('product_id')->constrained('products'); //produit considéré
$table->foreignId('promotion_id')->constrained('promotions'); //promotion associée
$table->collation = 'utf8mb4_unicode_ci';
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('product_promotion');
}
}

View file

@ -0,0 +1,36 @@
<?php
use Database\Seeders\TaxRatesSeeder;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateTaxRatesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('tax_rates', function (Blueprint $table) {
$table->id();
$table->string('label');
$table->float('rate');
});
$seeder = new TaxRatesSeeder();
$seeder->run();
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('tax_rates');
}
}

View file

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddFieldsToProductsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('products', function (Blueprint $table) {
$table->foreignId('tax_rate_id')->after('price_buy')->default(1)->constrained('tax_rates');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('products', function (Blueprint $table) {
$table->dropForeign(['tax_rate_id']);
$table->dropColumn('tax_rate_id');
});
}
}

View file

@ -0,0 +1,26 @@
<?php
namespace Database\Seeders;
use App\Models\DiscountType;
use Illuminate\Database\Seeder;
class DiscountTypesSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$montant = new DiscountType();
$montant->label = "Montant";
$pourcentage = new DiscountType();
$pourcentage->label = "Pourcentage";
$montant->save();
$pourcentage->save();
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace Database\Seeders;
use App\Models\TaxRate;
use Illuminate\Database\Seeder;
class TaxRatesSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$highTva = new TaxRate();
$highTva->label = "TVA 20%";
$highTva->rate = 20;
$lowTva = new TaxRate();
$lowTva->label = "TVA 5.5%";
$lowTva->rate = 5.5;
$highTva->save();
$lowTva->save();
}
}

15
package-lock.json generated
View file

@ -8271,6 +8271,12 @@
"integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=",
"dev": true
},
"smooth-dnd": {
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/smooth-dnd/-/smooth-dnd-0.12.1.tgz",
"integrity": "sha512-Dndj/MOG7VP83mvzfGCLGzV2HuK1lWachMtWl/Iuk6zV7noDycIBnflwaPuDzoaapEl3Pc4+ybJArkkx9sxPZg==",
"dev": true
},
"snapdragon": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
@ -9356,6 +9362,15 @@
"vue-style-loader": "^4.1.0"
}
},
"vue-smooth-dnd": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/vue-smooth-dnd/-/vue-smooth-dnd-0.8.1.tgz",
"integrity": "sha512-eZVVPTwz4A1cs0+CjXx/ihV+gAl3QBoWQnU6+23Gp59t0WBU99z7ducBQ4FvjBamqOlg8SDOE5eFHQedxwB4Wg==",
"dev": true,
"requires": {
"smooth-dnd": "0.12.1"
}
},
"vue-style-loader": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.2.tgz",

View file

@ -11,7 +11,7 @@
},
"devDependencies": {
"axios": "^0.21.1",
"bootstrap": "^4.5.0",
"bootstrap": "^4.6.0",
"cross-env": "^7.0",
"jquery": "^3.6",
"laravel-mix": "^5.0.1",
@ -21,6 +21,7 @@
"sass-loader": "^8.0.2",
"swiper": "^6.3.4",
"toastify-js": "^1.10.0",
"vue-smooth-dnd": "^0.8.1",
"vue-template-compiler": "^2.6.12"
},
"dependencies": {

921
public/b-o/js/app.js vendored

File diff suppressed because one or more lines are too long

View file

@ -2321,6 +2321,10 @@ __webpack_require__.r(__webpack_exports__);
//
//
//
//
//
//
//
/* harmony default export */ __webpack_exports__["default"] = ({
props: [],
computed: {
@ -2341,7 +2345,8 @@ __webpack_require__.r(__webpack_exports__);
success: false,
errors: false,
showConfirmEmpty: false,
response: ''
response: '',
promotion_slide: -1
};
},
mounted: function mounted() {
@ -2437,6 +2442,28 @@ __webpack_require__.r(__webpack_exports__);
type: 'updateCartTotals',
cartTotals: result[1]
});
},
hover: function hover(zone, row_id) {
console.log(this.promotion_slide);
eval("this." + zone + "='" + row_id + "';");
console.log(this.promotion_slide);
},
//general purpose TODO : globalize
numFormatter: function numFormatter(num) {
return new Intl.NumberFormat('fr-FR', {
style: 'currency',
currency: 'EUR'
}).format(num);
},
dateFormatter: function dateFormatter(str) {
var d = new Date(str);
var options = {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
};
return d.toLocaleDateString("fr-FR", options);
}
}
});
@ -41554,22 +41581,6 @@ var render = function() {
[_vm._m(1), _vm._v(" "), _vm._m(2)]
),
_vm._v(" "),
_c(
"div",
{
directives: [
{
name: "show",
rawName: "v-show",
value: !_vm.emptybasket,
expression: "!(emptybasket)"
}
],
staticClass: "message-box"
},
[_vm._m(3)]
),
_vm._v(" "),
_c("div", {
directives: [
{
@ -41690,10 +41701,7 @@ var render = function() {
"article",
{
staticClass:
"ds-product-card--largehorizontal ds-product-card",
attrs: {
id: "3560070820542"
}
"ds-product-card--largehorizontal ds-product-card"
},
[
_c(
@ -41723,7 +41731,7 @@ var render = function() {
]
),
_vm._v(" "),
_vm._m(4, true),
_vm._m(3, true),
_vm._v(" "),
_c(
"div",
@ -41824,7 +41832,7 @@ var render = function() {
},
[
_vm._v(
"\r\n " +
"\n " +
_vm._s(
item
.cartItem
@ -41836,7 +41844,7 @@ var render = function() {
.product
.weight_unit
) +
"\r\n "
"\n "
)
]
)
@ -41881,13 +41889,13 @@ var render = function() {
},
[
_vm._v(
"\r\n " +
"\n " +
_vm._s(
item
.cartItem
.name
) +
"\r\n "
"\n "
)
]
)
@ -41911,13 +41919,13 @@ var render = function() {
},
[
_vm._v(
"\r\n " +
"\n " +
_vm._s(
item
.product
.sell_unit
) +
"\r\n "
"\n "
)
]
)
@ -41934,6 +41942,318 @@ var render = function() {
"ds-product-card--largehorizontal-infos-sec"
},
[
_vm._l(
item.product
.promotions,
function(
promotion
) {
return _c(
"div",
{
directives: [
{
name:
"show",
rawName:
"v-show",
value:
item
.product
.calculatedPrices
.discountAmount >
0,
expression:
"item.product.calculatedPrices.discountAmount>0"
}
],
staticClass:
"ds-product-card--largehorizontal-promotion"
},
[
_c(
"div",
[
_c(
"div",
{
staticClass:
"promotion-description promotion-description--loyalty"
},
[
_c(
"div",
{
staticClass:
"promotion-description__labels",
on: {
mouseover: function(
$event
) {
return _vm.hover(
"promotion_slide",
item
.cartItem
.rowId
)
},
mouseleave: function(
$event
) {
return _vm.hover(
"promotion_slide",
"-1"
)
}
}
},
[
_c(
"p",
{
staticClass:
"promotion-label",
attrs: {
title:
item
.product
.calculatedPrices
.discountRate +
"% d'économies"
}
},
[
_c(
"span",
{
staticClass:
"promotion-label__txt"
},
[
_vm._v(
_vm._s(
item
.product
.calculatedPrices
.discountRate
) +
"% d'économies"
)
]
)
]
)
]
),
_vm._v(
" "
),
_c(
"svg",
{
staticClass:
"promotion-description__icon-info",
attrs: {
focusable:
"false"
}
},
[
_c(
"use",
{
attrs: {
"xmlns:xlink":
"http://www.w3.org/1999/xlink",
"xlink:href":
"#icon-info"
}
}
)
]
)
]
),
_vm._v(
" "
),
_c(
"section",
{
directives: [
{
name:
"show",
rawName:
"v-show",
value:
_vm.promotion_slide ==
item
.cartItem
.rowId,
expression:
"promotion_slide == item.cartItem.rowId"
}
],
staticClass:
"promotion-slide"
},
[
_c(
"header",
{
staticClass:
"promotion-slide__header promotion-slide__header--loyalty"
},
[
_c(
"p",
{
staticClass:
"promotion-slide__header-txt"
},
[
_c(
"svg",
{
staticClass:
"promotion-slide__icon-promo",
attrs: {
focusable:
"false"
}
},
[
_c(
"use",
{
attrs: {
"xmlns:xlink":
"http://www.w3.org/1999/xlink",
"xlink:href":
"#icon-carrefour-loyalty-card-hightlighted"
}
}
)
]
),
_vm._v(
" "
),
_c(
"span",
{
staticClass:
"promotion-slide__header-label"
},
[
_vm._v(
"\n Economisé\n "
)
]
),
_vm._v(
" "
),
_c(
"span",
{
staticClass:
"promotion-slide__header-discount"
},
[
_vm._v(
"\n " +
_vm._s(
_vm.numFormatter(
(Math.round(
(item
.product
.calculatedPrices
.priceTtc -
item
.product
.calculatedPrices
.discountedPriceTtc) *
100
) /
100) *
item
.cartItem
.qty
)
) +
"\n "
)
]
)
]
)
]
),
_vm._v(
" "
),
_c(
"p",
{
staticClass:
"promotion-slide__txt"
},
[
_c(
"span",
[
_c(
"b",
[
_vm._v(
_vm._s(
item
.product
.calculatedPrices
.discountRate
) +
"% d'économies"
)
]
),
_c(
"br"
)
]
),
_vm._v(
" "
),
_c(
"span",
{
staticClass:
"promotion-slide__end-date"
},
[
_vm._v(
"Offre valable jusqu'au " +
_vm._s(
_vm.dateFormatter(
promotion.date_end
)
)
)
]
)
]
)
]
)
]
)
]
)
}
),
_vm._v(" "),
_c(
"div",
{
@ -41963,18 +42283,16 @@ var render = function() {
},
[
_vm._v(
"\r\n " +
"\n " +
_vm._s(
item
.cartItem
.price *
(1 +
item
.cartItem
.taxRate /
100)
_vm.numFormatter(
item
.product
.calculatedPrices
.discountedPriceTtc
)
) +
"€ TTC\r\n "
"\n "
)
]
)
@ -42073,13 +42391,13 @@ var render = function() {
},
[
_vm._v(
"\r\n " +
"\n " +
_vm._s(
item
.cartItem
.qty
) +
"\r\n "
"\n "
)
]
),
@ -42143,14 +42461,15 @@ var render = function() {
" "
),
_vm._m(
5,
4,
true
)
]
)
]
)
]
],
2
)
]
)
@ -42187,7 +42506,7 @@ var render = function() {
"products-brief-data products-brief--items"
},
[
_vm._m(6),
_vm._m(5),
_vm._v(" "),
_c("dd", [
_c(
@ -42198,9 +42517,13 @@ var render = function() {
},
[
_vm._v(
"\r\n " +
_vm._s(_vm.cartTotals.totalTTC) +
" €\r\n "
"\n " +
_vm._s(
_vm.numFormatter(
_vm.cartTotals.totalTTC
)
) +
"\n "
)
]
)
@ -42256,7 +42579,7 @@ var render = function() {
]
),
_vm._v(
"\r\n Vider le panier\r\n "
"\n Vider le panier\n "
)
]
)
@ -42290,7 +42613,7 @@ var render = function() {
"div",
{ staticClass: "modal__container" },
[
_vm._m(7),
_vm._m(6),
_vm._v(" "),
_c(
"div",
@ -42322,14 +42645,14 @@ var render = function() {
},
[
_vm._v(
"\r\n Oui, vider mon panier\r\n "
"\n Oui, vider mon panier\n "
)
]
)
]
),
_vm._v(" "),
_vm._m(8)
_vm._m(7)
])
]
)
@ -42343,7 +42666,7 @@ var render = function() {
)
]),
_vm._v(" "),
_vm._m(9)
_vm._m(8)
])
])
])
@ -42364,7 +42687,7 @@ var render = function() {
},
[
_vm._v(
"\r\n Total\r\n "
"\n Total\n "
)
]
),
@ -42377,15 +42700,17 @@ var render = function() {
},
[
_vm._v(
"\r\n " +
_vm._s(_vm.cartTotals.totalTTC) +
" €\r\n "
"\n " +
_vm._s(
_vm.numFormatter(_vm.cartTotals.totalTTC)
) +
"\n "
)
]
)
]),
_vm._v(" "),
_vm._m(10)
_vm._m(9)
]
)
]
@ -42470,9 +42795,7 @@ var staticRenderFns = [
var _c = _vm._self._c || _h
return _c("div", { staticClass: "basket-panel__title-wrapper" }, [
_c("span", { staticClass: "title title_medium" }, [
_vm._v(
"\r\n Aperçu panier\r\n "
)
_vm._v("\n Aperçu panier\n ")
])
])
},
@ -42486,52 +42809,6 @@ var staticRenderFns = [
[_c("span", [_vm._v("Fermer")])]
)
},
function() {
var _vm = this
var _h = _vm.$createElement
var _c = _vm._self._c || _h
return _c("div", { staticClass: "message-box__inner" }, [
_c(
"div",
{
staticClass:
"message-box__heading message-box__icon message-box__icon--low"
},
[
_vm._v(
"\r\n Vous n’êtes pas connecté\r\n "
)
]
),
_vm._v(" "),
_c("div", { staticClass: "message-box__body" }, [
_vm._v(
"\r\n Connectez-vous ou créez votre compte pour passer votre commande\r\n "
)
]),
_vm._v(" "),
_c("a", {
staticClass: "message-box__alert-title",
attrs: { href: "#", target: "" }
}),
_vm._v(" "),
_c("div", { staticClass: "message-box__actions" }, [
_c("div", [
_c("a", { attrs: { href: "/connexion" } }, [
_vm._v(
"\r\n Me connecter\r\n "
)
]),
_vm._v(" "),
_c("a", { attrs: { href: "/inscription" } }, [
_vm._v(
"\r\n Créer mon compte\r\n "
)
])
])
])
])
},
function() {
var _vm = this
var _h = _vm.$createElement
@ -42586,7 +42863,7 @@ var staticRenderFns = [
return _c("div", { staticClass: "modal__body" }, [
_c("div", { staticClass: "checkout-clear-basket__modal__title" }, [
_vm._v(
"\r\n Voulez-vous réellement vider votre panier ?\r\n "
"\n Voulez-vous réellement vider votre panier ?\n "
)
])
])
@ -42620,7 +42897,7 @@ var staticRenderFns = [
_c("section", { staticClass: "checkout-loyalty-info__top-section" }, [
_c("h3", { staticClass: "ds-title ds-title--bold ds-title--xs" }, [
_vm._v(
"\r\n Avantages fidélité\r\n "
"\n Avantages fidélité\n "
)
])
])
@ -42639,7 +42916,7 @@ var staticRenderFns = [
_c("a", { attrs: { href: "/commande/recap" } }, [
_c("span", { staticClass: "pl-button__label" }, [
_vm._v(
"\r\n Commander\r\n "
"\n Commander\n "
)
])
])
@ -56304,11 +56581,14 @@ module.exports = function(module) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var swiper_swiper_bundle__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! swiper/swiper-bundle */ "./node_modules/swiper/swiper-bundle.js");
/* harmony import */ var swiper_swiper_bundle__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(swiper_swiper_bundle__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var toastify_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! toastify-js */ "./node_modules/toastify-js/src/toastify.js");
/* harmony import */ var toastify_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(toastify_js__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var vuex__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! vuex */ "./node_modules/vuex/dist/vuex.esm.js");
/* harmony import */ var jquery__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! jquery */ "./node_modules/jquery/dist/jquery.js");
/* harmony import */ var jquery__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(jquery__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var swiper_swiper_bundle__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! swiper/swiper-bundle */ "./node_modules/swiper/swiper-bundle.js");
/* harmony import */ var swiper_swiper_bundle__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(swiper_swiper_bundle__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var toastify_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! toastify-js */ "./node_modules/toastify-js/src/toastify.js");
/* harmony import */ var toastify_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(toastify_js__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var vuex__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! vuex */ "./node_modules/vuex/dist/vuex.esm.js");
@ -56316,7 +56596,7 @@ __webpack_require__(/*! ./bootstrap */ "./resources/views/front/canard/js/bootst
__webpack_require__(/*! ./cart.js */ "./resources/views/front/canard/js/cart.js");
window.Toastify = toastify_js__WEBPACK_IMPORTED_MODULE_1___default.a;
window.Toastify = toastify_js__WEBPACK_IMPORTED_MODULE_2___default.a;
window.Vue = __webpack_require__(/*! vue */ "./node_modules/vue/dist/vue.common.js"); // Vuex
@ -56338,7 +56618,7 @@ Vue.component('basket-counter', {
},
template: '<span class="header-actions__item-icon icon icon_cart"><span class="icon__count">{{ count }}</span></span>'
});
var store = new vuex__WEBPACK_IMPORTED_MODULE_2__["default"].Store({
var store = new vuex__WEBPACK_IMPORTED_MODULE_3__["default"].Store({
state: {
panier: {},
cartTotals: {}
@ -56372,7 +56652,7 @@ var app = new Vue({
$(document).ready(function () {
//document is ready we can free css transitions
$('.preload').removeClass("preload");
var mainslider = new swiper_swiper_bundle__WEBPACK_IMPORTED_MODULE_0___default.a('.mainslider .swiper-container', {
var mainslider = new swiper_swiper_bundle__WEBPACK_IMPORTED_MODULE_1___default.a('.mainslider .swiper-container', {
pagination: {
el: '.mainslider__pagination',
bulletClass: 'slider-bullet',
@ -56380,7 +56660,7 @@ $(document).ready(function () {
clickable: true
}
});
var rp_slider = new swiper_swiper_bundle__WEBPACK_IMPORTED_MODULE_0___default.a('.rp-slider .swiper-container', {
var rp_slider = new swiper_swiper_bundle__WEBPACK_IMPORTED_MODULE_1___default.a('.rp-slider .swiper-container', {
slidesPerView: 4,
spaceBetween: 40,
navigation: {
@ -56412,7 +56692,7 @@ $(document).ready(function () {
}
}
});
var rp_slider_mobile = new swiper_swiper_bundle__WEBPACK_IMPORTED_MODULE_0___default.a('.related-products-mobile .swiper-container', {
var rp_slider_mobile = new swiper_swiper_bundle__WEBPACK_IMPORTED_MODULE_1___default.a('.related-products-mobile .swiper-container', {
slidesPerView: 4,
spaceBetween: 20,
navigation: {

6
resources/js/app.js vendored
View file

@ -1,7 +1,13 @@
require('./bootstrap');
window.Vue = require('vue');
// Vuex
// import Vuex from 'vuex';
// import { Container, Draggable } from 'vue-smooth-dnd';
// import { applyDrag, generateItems } from './helpers';
Vue.component('faq', require('./components/Faq.vue').default);
Vue.component('products-cards-dnd', require('./components/ProductsCardsDnd.vue').default);
const app = new Vue({
el: '#app',

View file

@ -0,0 +1,214 @@
<template>
<div class="wide-page">
<div class="columns">
<div v-for="(items, index) in groups"
:key="index"
class="column"
>
<div>
<label>
<input type="checkbox" v-model="flags[index].drop"> Accept Drop
</label>
<label>
<input type="checkbox" v-model="flags[index].animate"> Animate Drop
</label>
</div>
<Container :data-index="index" group-name="column"
:get-child-payload="itemIndex => getChildPayload(index, itemIndex)"
:should-accept-drop="(src, payload) => getShouldAcceptDrop(index, src, payload)"
:should-animate-drop="(src, payload) => getShouldAnimateDrop(index, src, payload)"
@drag-start="onDragStart"
@drag-enter="onDragEnter(index)"
@drag-leave="onDragLeave(index)"
@drag-end="onDragEnd"
@drop="onDrop(index, $event)"
>
<Draggable v-for="item in items" :key="item.id">
<div class="draggable-item">
{{ item.data }}
</div>
</Draggable>
</Container>
</div>
</div>
<div class="controls">
<div>
<button @click="removeColumn()" :disabled="groups.length === 1">Remove Column</button>
<button @click="addColumn()">Add Column</button>
</div>
<div>
<label v-for="(key, name) in logs" :key="name">
<input type="checkbox" v-model="logs[name]"> {{ name }}
</label>
<hr>
<label>
<input type="checkbox" v-model="logPayload"> Log payload
</label>
</div>
</div>
</div>
</template>
<script>
import { Container, Draggable } from 'vue-smooth-dnd'
import { applyDrag, generateItems } from '../helpers'
import Vue from 'vue'
let i = 0
function id () {
return `item-${++i}`
}
function generate (num) {
return generateItems(5, i => ({
id: id(),
data: `Draggable ${num} - ${i + 1}`
}))
}
export default {
name: 'Events',
components: {
Container,
Draggable
},
data () {
return {
groups: [],
flags: [],
logs: {
'get-child-payload': true,
'should-accept-drop': false,
'should-animate-drop': false,
'drag-start': true,
'drag-end': true,
'drag-enter': true,
'drag-leave': true,
'drop': true
},
logPayload: true
}
},
created () {
this.addColumn()
},
methods: {
// -----------------------------------------------------------------------------------------------------------------
// callbacks
getChildPayload (groupIndex, itemIndex) {
this.log('get-child-payload', groupIndex, itemIndex)
return this.groups[groupIndex][itemIndex]
},
getShouldAcceptDrop (index, sourceContainerOptions, payload) {
this.log('should-accept-drop', sourceContainerOptions, payload)
return this.flags[index].drop
},
getShouldAnimateDrop (index, sourceContainerOptions, payload) {
this.log('should-animate-drop', sourceContainerOptions, payload)
return this.flags[index].animate
},
// -----------------------------------------------------------------------------------------------------------------
// events
onDragStart (...args) {
this.log('drag-start', ...args)
},
onDragEnd (...args) {
this.log('drag-end', ...args)
},
onDragEnter (...args) {
this.log('drag-enter', ...args)
},
onDragLeave (...args) {
this.log('drag-leave', ...args)
},
onDrop (groupIndex, dropResult) {
let result = applyDrag(this.groups[groupIndex], dropResult)
Vue.set(this.groups, groupIndex, result)
this.log('drop', dropResult)
},
// -----------------------------------------------------------------------------------------------------------------
// app
addColumn () {
this.groups.push(generate(this.groups.length + 1))
this.flags.push({drop: true, animate: true})
},
removeColumn () {
this.groups.pop()
this.flags.pop()
},
log (name, ...args) {
if (this.logs[name]) {
this.logPayload
? console.log(name, ...args)
: console.log(name)
}
}
}
}
</script>
<style scoped>
.controls {
margin-top: 1em;
}
.controls > div {
padding-top: 1em;
}
label {
display: block;
line-height: 1.6em;
}
.columns {
display: flex;
justify-content: stretch;
}
.column {
margin-right: 20px;
flex: 1;
}
.column {
display: flex;
flex-direction: column;
flex-grow: 1;
}
.column .smooth-dnd-container.vertical {
display: flex;
flex-direction: column;
flex-grow: 1;
}
</style>

26
resources/js/helpers.js vendored Normal file
View file

@ -0,0 +1,26 @@
//for testing purposes
export const applyDrag = (arr, dragResult) => {
const { removedIndex, addedIndex, payload } = dragResult
if (removedIndex === null && addedIndex === null) return arr
const result = [...arr]
let itemToAdd = payload
if (removedIndex !== null) {
itemToAdd = result.splice(removedIndex, 1)[0]
}
if (addedIndex !== null) {
result.splice(addedIndex, 0, itemToAdd)
}
return result
}
export const generateItems = (count, creator) => {
const result = []
for (let i = 0; i < count; i++) {
result.push(creator(i))
}
return result
}

View file

@ -51,5 +51,7 @@
@endforeach
</tbody>
</table>
{{ $products->links() }}
@endsection

View file

@ -0,0 +1,54 @@
@extends('layouts.bo-layout')
@section('content')
<form action="{{route('bo.promotions.store')}}" method="POST" enctype="multipart/form-data">
@csrf
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2"><a href="{{route('bo.promotions.index')}}" class="h2 text-decoration-none"><</a> Promotions <small class="text-muted">Création</small></h1>
<div class="btn-toolbar mb-2 mb-md-0">
<button type="submit" class="btn btn-sm btn-primary">Enregistrer</a>
</div>
</div>
@if ($errors->any())
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
{{-- //TODO implementer plus tard <products-cards-dnd></products-cards-dnd> --}}
<div class="form-group">
<label for="name">Produit *</label>
<input type="text" id="product_id" name="product_id" class="form-control" placeholder="Produit * " value="{{ old('product_id') }}" required>
</div>
<div class="form-row">
<div class="form-group col">
<label for="discount_type_id">Type de remise *</label>
<select class="custom-select" id="discount_type_id" name="discount_type_id">
@foreach($discTypes as $discType)
<option value="{{$discType->id}}">{{$discType->label}}</option>
@endforeach
</select>
</div>
<div class="form-group col">
<label for="discount_value">Valeur *</label>
<input type="number" id="discount_value" name="discount_value" min="0" step=".01" class="form-control" value="{{ old('discount_value') }}" required>
<small class="text-muted"> montant ou pourcentage de remise selon le type choisi </small>
</div>
<div class="form-group col">
<label for="date_start_date">Début <small class="text-muted"> de la promotion </small></label>
<input type="date" id="date_start_date" name="date_start_date" class="form-control" value="{{ old('date_start_date') }}" required>
<input type="time" id="date_start_time" name="date_start_time" class="form-control" value="{{ old('date_start_time') }}" required>
</div>
<div class="form-group col">
<label for="date_end_date">Fin <small class="text-muted"> de la promotion </small></label>
<input type="date" id="date_end_date" name="date_end_date" class="form-control" value="{{ old('date_end_date') }}" required>
<input type="time" id="date_end_time" name="date_end_time" class="form-control" value="{{ old('date_end_time') }}" required>
</div>
</div>
</form>
@endsection

View file

@ -0,0 +1,56 @@
@extends('layouts.bo-layout')
@section('content')
<form action="{{route('bo.promotions.update', $promotion->id)}}" method="POST" enctype="multipart/form-data">
@method('PUT')
@csrf
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2"><a href="{{route('bo.promotions.index')}}" class="h2 text-decoration-none"><</a> Promotion <small class="text-muted">Modification</small></h1>
@if (session('success'))
<span class="float-center text-success">{{ session('success') }}</span>
@endif
<div class="btn-toolbar mb-2 mb-md-0">
<button type="submit" class="btn btn-sm btn-primary">Enregistrer</a>
</div>
</div>
@if ($errors->any())
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="form-group">
<label for="name">Produit *</label>
<input type="text" id="product_id" name="product_id" class="form-control" placeholder="Produit * " value="{{ old('product_id', implode(",",$promotion->products->pluck('id')->all())) }}" required>
</div>
<div class="form-row">
<div class="form-group col">
<label for="discount_type_id">Type de remise *</label>
<select class="custom-select" id="discount_type_id" name="discount_type_id">
@foreach($discTypes as $discType)
<option value="{{$discType->id}}" @if ($discType->id === $promotion->discount_type_id) selected @endif>{{$discType->label}}</option>
@endforeach
</select>
</div>
<div class="form-group col">
<label for="discount_value">Valeur *</label>
<input type="number" id="discount_value" name="discount_value" min="0" step=".01" class="form-control" value="{{ old('discount_value',$promotion->discount_value) }}" required>
<small class="text-muted"> montant ou pourcentage de remise selon le type choisi </small>
</div>
<div class="form-group col">
<label for="date_start_date">Début <small class="text-muted"> de la promotion </small></label>
<input type="date" id="date_start_date" name="date_start_date" class="form-control" value="{{ old('date_start_date', date_format($promotion->date_start, "Y-m-d")) }}" required>
<input type="time" id="date_start_time" name="date_start_time" class="form-control" value="{{ old('date_start_time', date_format($promotion->date_start, "H:i")) }}" required>
</div>
<div class="form-group col">
<label for="date_end_date">Fin <small class="text-muted"> de la promotion </small></label>
<input type="date" id="date_end_date" name="date_end_date" class="form-control" value="{{ old('date_end_date', date_format($promotion->date_end, "Y-m-d")) }}" required>
<input type="time" id="date_end_time" name="date_end_time" class="form-control" value="{{ old('date_end_time', date_format($promotion->date_end, "H:i")) }}" required>
</div>
</div>
</form>
@endsection

View file

@ -0,0 +1,61 @@
@extends('layouts.bo-layout')
@section('content')
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Promotions <small class="text-muted">Liste</small></h1>
@if ($errors->any())
@foreach($errors->all() as $error)
<span class="float-center text-danger">{{ $error }}</span>
@endforeach
@endif
<a href="{{route('bo.promotions.create')}}" class="btn btn-primary float-right">Créer une promotion</a>
</div>
<table class="table">
<thead>
<th scope="col">Type</th>
<th scope="col">Valeur</th>
<th scope="col">Début</th>
<th scope="col">Fin</th>
<th scope="col">Produits Concernés</th>
<th></th>
</thead>
<tbody>
@foreach ($promotions as $promotion)
<tr>
<td>{{$promotion->discountType->label}}</>
<td>{{$promotion->discount_value}}</>
<td>{{$promotion->date_start}}</td>
<td>{{$promotion->date_end}}</td>
<td style="display: flex;">
@foreach ($promotion->products as $product)
<div class="card col-md-3">
<img class="card-img-top" src="/images/produits/{{$product->picture}}" alt="{{$product->name}}">
<div class="card-body">
<h5 class="card-title"><a href="{{route('bo.products.edit', $product->id)}}">{{$product->name}}</a></h5>
<small class="text-muted">Prix HT avant promo : {{$product->price}}</small>
<p class="card-text">Prix HT après promo :
@if ($promotion->discountType->id == 1)
{{$product->price-$promotion->discount_value}}
@else
{{$product->price-($product->price*$promotion->discount_value/100)}}
@endif
</p>
</div>
</div>
@endforeach
</td>
<td class="text-center">
<a href="{{route('bo.promotions.edit', $promotion->id)}}" class="btn btn-sm btn-primary">Éditer</a>
<form action="{{route('bo.promotions.destroy', $promotion->id)}}" method="POST" class="d-inline">
@method('DELETE')
@csrf
<input type="submit" class="btn btn-sm btn-danger" value="Supprimer"
onclick="return confirm('Êtes-vous sûr(e) ?')">
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
@endsection

View file

@ -1,3 +1,4 @@
import { isWindow } from 'jquery';
import Swiper from 'swiper/swiper-bundle';
import Toastify from "toastify-js";
require('./bootstrap');

View file

@ -60,7 +60,7 @@
<span>Fermer</span>
</button>
</div>
<div class="message-box" v-show="!(emptybasket)">
<!-- <div class="message-box" v-show="!(emptybasket)">
<div class="message-box__inner">
<div class="message-box__heading message-box__icon message-box__icon--low">
Vous n’êtes pas connecté
@ -79,9 +79,9 @@
</a></div>
</div>
</div>
</div>
</div> -->
<div class="no-slot-alert" v-show="!(emptybasket)">
<!-- what is the purpose of this one ? -->
<!-- show next available collection slot ? -->
</div>
<div class="basket-panel__content basket-panel__content--smooth-scroll" v-show="!(emptybasket)">
<section class="cart-tile cart-tile--open" drivetype="drive">
@ -132,7 +132,7 @@
<ol>
<!-- boucleproduits -->
<li class="basket-products-list__product-card basket-products-list__product-card--no-margin" v-for="item in panier">
<article class="ds-product-card--largehorizontal ds-product-card" id="3560070820542">
<article class="ds-product-card--largehorizontal ds-product-card">
<div class="ds-product-card--largehorizontal-icons">
<div title="Supprimer" class="product-card-delete" @click="removeItem(item.cartItem.rowId)"></div>
</div>
@ -193,38 +193,43 @@
</div>
</div>
<div class="ds-product-card--largehorizontal-infos-sec">
<!-- promotion
<div class="ds-product-card--largehorizontal-promotion">
<!-- promotion -->
<div class="ds-product-card--largehorizontal-promotion" v-show="item.product.calculatedPrices.discountAmount>0" v-for="promotion in item.product.promotions">
<div>
<div class="promotion-description promotion-description--loyalty">
<div class="promotion-description__labels">
<p title="20% d'économies" class="promotion-label"><span class="promotion-label__txt">20% d'économies</span></p>
</div> <svg focusable="false" class="promotion-description__icon-info">
<div class="promotion-description__labels" @mouseover="hover('promotion_slide',item.cartItem.rowId)" @mouseleave="hover('promotion_slide','-1')">
<p :title="item.product.calculatedPrices.discountRate+'% d\'économies'" class="promotion-label">
<span class="promotion-label__txt">{{ item.product.calculatedPrices.discountRate }}% d'économies</span>
</p>
</div>
<svg focusable="false" class="promotion-description__icon-info">
<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#icon-info"></use>
</svg>
</div>
<section class="promotion-slide" style="display: none;">
<section class="promotion-slide" v-show="promotion_slide == item.cartItem.rowId">
<header class="promotion-slide__header promotion-slide__header--loyalty">
<p class="promotion-slide__header-txt"><svg focusable="false" class="promotion-slide__icon-promo">
<p class="promotion-slide__header-txt">
<svg focusable="false" class="promotion-slide__icon-promo">
<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#icon-carrefour-loyalty-card-hightlighted"></use>
</svg>
<span class="promotion-slide__header-label">
Cagnottés sur la carte
Economisé
</span>
<span class="promotion-slide__header-discount">
{{ numFormatter((Math.round((item.product.calculatedPrices.priceTtc - item.product.calculatedPrices.discountedPriceTtc)*100)/100) * item.cartItem.qty) }}
</span>
<span class="promotion-slide__header-discount">0,01€</span>
</p>
</header>
<p class="promotion-slide__txt"><span><b>20% d'économies</b><br></span>
<span class="promotion-slide__end-date">Offre valable jusqu'au 09/03/2021</span>
<p class="promotion-slide__txt"><span><b>{{ item.product.calculatedPrices.discountRate }}% d'économies</b><br></span>
<span class="promotion-slide__end-date">Offre valable jusqu'au {{ dateFormatter(promotion.date_end) }}</span>
</p>
</section>
</div>
</div>
-->
<div class="ds-product-card--largehorizontal-pricing">
<div class="product-card-price">
<div class="product-card-price__price"><span class="ds-title product-card-price__price--final ds-title--m product-card-price__discount--fid">
{{ (item.cartItem.price * (1+item.cartItem.taxRate/100)) }}€ TTC
{{ numFormatter(item.product.calculatedPrices.discountedPriceTtc) }}
</span>
</div>
</div>
@ -275,7 +280,7 @@
</dt>
<dd>
<div class="ds-body-text ds-body-text--weight-bold ds-body-text--size-m">
{{cartTotals.totalTTC}} €
{{numFormatter(cartTotals.totalTTC)}}
</div>
</dd>
</dl>
@ -335,7 +340,7 @@
Total
</h2>
<h2 class="ds-title ds-title--bold ds-title--m ds-title--bright">
{{cartTotals.totalTTC}} €
{{numFormatter(cartTotals.totalTTC)}}
</h2>
</div>
<button class="pl-button pl-button--primary pl-button--light">
@ -384,6 +389,7 @@ export default {
errors:false,
showConfirmEmpty:false,
response: '',
promotion_slide: -1
}
},
mounted() {
@ -476,6 +482,20 @@ export default {
type: 'updateCartTotals',
cartTotals: result[1]
});
},
hover(zone,row_id) {
console.log(this.promotion_slide);
eval("this."+zone+"='"+row_id+"';");
console.log(this.promotion_slide);
},
//general purpose TODO : globalize
numFormatter(num) {
return new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR' }).format(num);
},
dateFormatter(str) {
let d=new Date(str);
var options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
return d.toLocaleDateString("fr-FR",options);
}
}
}

View file

@ -1,52 +1,55 @@
<div class="product-list">
{{-- Section offre spéciale --}}
@foreach ($offres as $product)
<div class="product-list__block product-list__block_wide desktop">
<form class="pl-item pl-item_wide">
<div class="pl-item pl-item_wide">
<div class="pl-item__panel">Offre spéciale</div>
<div class="pl-item__content">
<a class="pl-item__title title title_xs" href="#">Bloc de foie gras avec 30% de morceaux 450 g en terrine </a>
<a class="pl-item__title title title_xs" href="{{route('product', ['productSlug' => $product->slug])}}">{{ $product->name }}</a>
<div class="pl-item__row">
<div class="pl-item__row-block">
<a class="pl-item__image" href="#"><img src="{{ asset(config('app.theme').'/img/demo/product_wide.png') }}" /></a>
<a class="pl-item__image" href="{{route('product', ['productSlug' => $product->slug])}}"><img src="{{ asset('/images/produits/' . $product->picture )}}" /></a>
</div>
<div class="pl-item__row-block">
<div class="pl-item-description">
Etiam sed sodales turpis. Ut pulvinar scelerisque dui. Nulla volutpat velit a ligula tristique rutrum. Cras mi tellus, viverra ut imperdiet in, dignissim nec nulla.
{!! $product->description !!}
</div>
<div class="pl-item__bottom">
<div class="pl-item__price">
<span class="pl-item__newprice title title_small">3,80€/pièce</span>
10,00€/pièce
<span class="pl-item__newprice title title_small">@fmt_currency($product->discountedPriceTtc) / {{ $product->sell_unit }}</span>
@fmt_currency($product->priceTtc) / {{ $product->sell_unit }}
</div>
<button class="pl-item__btn btn btn_blue"></button>
<button class="pl-item__btn btn btn_blue" @click="addtocart({{ $product->id }},1)"></button>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
<div class="product-list__block mobile">
<form class="pl-item pl-item_offre">
<div class="pl-item pl-item_offre">
<div class="pl-item__panel">Offre spéciale</div>
<a class="pl-item__title title title_xs" href="#">Bloc de foie gras avec 30% de morceaux 450 g en terrine</a>
<a class="pl-item__image" href="#"><img src="{{ asset(config('app.theme').'/img/demo/pl_1.png') }}" /></a>
<a class="pl-item__title title title_xs" href="{{route('product', ['productSlug' => $product->slug])}}">{{ $product->name }}</a>
<a class="pl-item__image" href="{{route('product', ['productSlug' => $product->slug])}}"><img src="{{ asset('/images/produits/' . $product->picture )}}" /></a>
<div class="pl-item__bottom">
<div class="pl-item__prices">
<span class="pl-item__newprice title title_small">3,80€/pièce</span>
<div class="pl-item__price">10,00€/pièce</div>
<span class="pl-item__newprice title title_small">@fmt_currency($product->discountedPriceTtc) / {{ $product->sell_unit }}</span>
<div class="pl-item__price">@fmt_currency($product->priceTtc)/{{ $product->sell_unit }}</div>
</div>
<button class="pl-item__btn btn btn_blue"></button>
<button class="pl-item__btn btn btn_blue" @click="addtocart({{ $product->id }},1)"></button>
</div>
</form>
</div>
</div>
@endforeach
{{-- Fin Section offre spéciale --}}
@foreach ($products as $product)
<div class="product-list__block">
<div class="pl-item">
<a class="pl-item__title title title_xs" href="{{route('product', ['productSlug' => $product->slug])}}">{{ $product->name }}</a>
<a class="pl-item__image" href="{{route('product', ['productSlug' => $product->slug])}}"><img src="{{ asset('/images/produits/' . $product->picture )}}" /></a>
<div class="pl-item__bottom">
<div class="pl-item__price">{{ $product->formattedPriceTTC }}/{{ $product->sell_unit }}</div>
<div class="pl-item__price">@fmt_currency($product->discountedPriceTtc)/{{ $product->sell_unit }}</div>
<button class="pl-item__btn btn btn_blue" @click="addtocart({{ $product->id }},1)"></button>
</div>
</div>

View file

@ -25,7 +25,7 @@
</div>
<div class="product__trade">
<div class="product__price title title_small mb0">{{ $product->formattedPriceTTC }}/pièce</div>
<div class="product__price title title_small mb0">@fmt_currency($product->discountedPriceTtc)/{{$product->sell_unit}}</div>
<div class="product__qty">
<span class="product__qty-btn product__qty-btn_minus" @click="num--">-</span>
<input class="product__qty-input form__input" type="text" value="1" name="product_qty" id="product_qty" v-model="num">

View file

@ -26,12 +26,12 @@ Une confirmation vient de vous être envoyée sur l’adresse mail {{ $order->cu
<div class="order__line">
<div class="qtt">{{ $line->quantity }} x</div>
<div class="designation">{{ $line->product->name }}</div>
<div class="price">{{ $line->total_ttc }} €</div>
<div class="price">{{ $line->formattedTotalTtc }}</div>
</div>
@endforeach
<div class="order_total">
<div>Total</div>
<div>{{ $order->total }} €</div>
<div>{{ $order->formattedTotalTtc }}</div>
</div>
<div class="message">
A retirer le <b>{{$order->timeslot->day}} à {{$order->timeslot->time}}</b>, à l’adresse suivante :</br>

View file

@ -53,7 +53,7 @@
</div>
<div class="ds-product-card__shimzone--small">
<div class="ds-body-text ds-product-card__perunitlabel ds-body-text--size-s ds-body-text--color-standard-3">
{{ $cartItem->price }} € / {{ $cartItem->model->sell_unit }}
@fmt_currency($cartItem->model->discountedPriceTtc) / {{ $cartItem->model->sell_unit }}
</div>
</div>
<div class="ds-product-card--largehorizontal-badges">
@ -95,8 +95,8 @@
</div>
</div>
</div>
<div class="order_recap__row order_recap_unitprice">{{ $cartItem->price }} €</div>
<div class="order_recap__row order_recap_totalprice">{{ $cartItem->price * $cartItem->qty }} €</div>
<div class="order_recap__row order_recap_unitprice">@fmt_currency($cartItem->model->discountedPriceTtc)</div>
<div class="order_recap__row order_recap_totalprice">{{ sprintf('%s €', number_format($cartItem->model->discountedPriceTtc * $cartItem->qty, 2, ',', ' ')) }}</div>
@endforeach
<!--start footer-->
<div class="order_recap__footer order_recap_action"></div>

View file

@ -60,6 +60,49 @@
<a class="nav-link" href="{{route('bo.ranges.index')}}">Gammes de produits</a>
</li>
</ul>
<div class="accordion" id="accordionExample">
{{-- <div class="card">
<div class="card-header" id="headingProduits">
<h2 class="mb-0">
<button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#collapseProduits" aria-expanded="true" aria-controls="collapseOne">
Produits
</button>
</h2>
</div>
<div id="collapseProduits" class="collapse" aria-labelledby="headingProduits" tata-parent="#accordionExample">
<div class="card-body">
<ul class="nav flex-column mb-2">
<li class="nav-item">
<a class="nav-link" href="{{route('bo.products.index')}}">Produits</a>
<a class="nav-link" href="{{route('bo.ranges.index')}}">Gammes de produits</a>
</li>
</ul>
</div>
</div>
</div> --}}
<div class="card">
<div class="card-header" id="headingMerchandising">
<h2 class="mb-0">
<button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#collapseMerchandising" aria-expanded="true" aria-controls="collapseOne">
Merchandising
</button>
</h2>
</div>
<div id="collapseMerchandising" class="collapse {{ (request()->routeIs('bo.promotions*')) ? 'show' : '' }}" aria-labelledby="headingMerchandising" tata-parent="#accordionExample">
<div class="card-body">
<ul class="nav flex-column mb-2">
<li class="nav-item">
<a class="nav-link" href="{{route('bo.promotions.index')}}">Promotions</a>
<a class="nav-link" href="#">Mise en avant</a>
<a class="nav-link" href="#">Associations de produits</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
<span>COMMANDES</span>

View file

@ -14,6 +14,7 @@ use App\Http\Controllers\Bo\ContentController;
use App\Http\Controllers\Bo\FaqController;
use App\Http\Controllers\Bo\SettingsController;
use App\Http\Controllers\Bo\OrdersController;
//use App\Http\Controllers\Bo\RangesController;
// AUTH
use App\Http\Controllers\Auth\LoginController;
@ -55,7 +56,7 @@ Route::prefix('ajax')
Route::post('/empty-cart', [BasketController::class, 'emptyCart'])->name('cart.empty');
});
Route::namespace('Bo')
Route::namespace('App\Http\Controllers\Bo')
->prefix('bo')
->name('bo.')
->middleware(['auth', 'is-admin'])
@ -73,7 +74,8 @@ Route::namespace('Bo')
]);
Route::get('/products/create/{product_type}', [\App\Http\Controllers\Bo\ProductsController::class, 'create'])
->name('products.create');
Route::resource('ranges', '\App\Http\Controllers\Bo\RangesController');
Route::resource('ranges', RangesController::class);
Route::resource('promotions', PromotionsController::class);
// Commandes
Route::get('/orders', [OrdersController::class, 'index'])->name('orders');
Route::get('/orders/{reference}', [OrdersController::class, 'changeStatus'])->name('change-order-status');