user()->isNoLongerEmployed()) { auth()->logout(); return redirect()->route('auth.connexion')->withErrors( 'Compte bloqué. Veuillez contacter votre RH ou le standard St Laurent au 02 43 62 70 00' ); } $activestep = 1; Cart::instance('panier'); $cart = Cart::content(); $cartInfos = array( "totalTTC" => Cart::total(), // Total des taxes. 'totalTVA' => Cart::tax(), // Total sans les taxes. 'totalHT' => Cart::subtotal(), // Poids total des items. 'totalweight' => Cart::weight(), // Total avant taxes et remises. 'totalHTbefore' => Cart::priceTotal(), // Total des remises. 'totaldiscount' => Cart::discount(), // Nb de produits dans le cart. 'countItems' => Cart::count(), ); return view('sales.recap', compact('activestep', 'cart', 'cartInfos')); } /** * front * Storing order from sales tunnel * * @return \Illuminate\Http\RedirectResponse */ public function store() { Cart::instance('panier'); $cart = Cart::content(); // Check that we have products in cart before saving. if (Cart::count() <= 0) { // Then redirect to empty recap. return redirect()->route('sales.recap')->with( 'message', 'Le nombre total de produits dans cette commande ne peut être négatif.' ); } // Save. $order = new Order(); // Generate order ref. $order->reference = $this->generateOrderRef(); $order->date = Carbon::now(); $order->status_id = 1; $order->total = str_replace(',', '.', str_replace(' ', '', Cart::total())); $order->customer_id = Auth::user()->id; $order->save(); // Order_id. foreach ($cart as $cartItem) { $order_line = new OrderLine(); $order_line->order_id = $order->id; $order_line->product_id = $cartItem->model->id; $order_line->quantity = $cartItem->qty; // 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; // Tax amount par unit. $order_line->unit_tax = round( (($order_line->unit_price - $order_line->unit_discount) * ($order_line->tax_rate / 100)) * 100 ) / 100; // Discount amount for the line. $order_line->total_discount = $order_line->unit_discount * $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(); } //end foreach // Then redirect. return redirect()->route('sales.pick-slot', ['reference' => $order->reference]); } /** * generate order Reference * for a new order * * @return string */ public function generateOrderRef() { // Format : YYYYMMDDHHMM000. $now = Carbon::now(); // Count todays orders. $countorders = Order::whereDate('created_at', Carbon::today())->count(); // Add one. $countorders++; $ref = $now->isoFormat('YYYYMMDDHHmm') . str_pad($countorders, 3, "0", STR_PAD_LEFT); return $ref; } /** * front tunnel de commande étape 3 : choix de créneau * * @param string $reference * @return \Illuminate\Contracts\View\View */ public function timeSlotPick(string $reference) { $activestep = 2; /* * Il faut diriger les salariés vers le bon magasin sans leur * laisser le choix * Dans la gestion des thèmes antérieure il était possible d'avoir 2 cas : * theme.multi_relay : on ou off, ici cela ne sera plus le cas * (thème non interchangeable pour cette boutique spécifique) */ // Si Magasin n'est pas référencé, on ne sait pas ou va le salarié. if (Auth::user()->salarie->magasin) { $magasin = Auth::user()->salarie->magasin; } $availCreneaux = array(); $order = Order::where('reference', '=', $reference)->firstOrFail(); return view('sales.pick-slot', compact('activestep', 'availCreneaux', 'order', 'magasin')); } /** * add timeslot by interval between start and end hour * * @param array $plage * @param integer $weekDay * @param CarbonImmutable $current * @param Magasin $magasin * @param CarbonImmutable $now * @param CarbonImmutable $today * @param CarbonInterval $delaiPrepa * @param CarbonInterval $interval * @param Carbon $currentTime * @param Carbon $endTime * @return array */ private function addCreneaux( array $plage, int $weekDay, CarbonImmutable $current, Magasin $magasin, CarbonImmutable $now, CarbonImmutable $today, CarbonInterval $delaiPrepa, CarbonInterval $interval, Carbon $currentTime, Carbon $endTime, bool $hasPublicHolidayNextWeekButNoFriday, bool $afterThursdayNight ) { $retArray = array(); do { $creneau = $currentTime->format('H:i'); $nbtakens = $magasin->timeslots() ->where('day', $current->format('d/m/Y')) ->where('time', $creneau)->count(); if ($nbtakens >= $plage[$weekDay]['max_orders_per_interval']) { $isDispo = false; } else { $isDispo = true; } /** * On utilise les méthodes de Carbon pour la comparaison de date, parce que c'est fait pour... */ if ( $current->addHours($currentTime->format('H')) ->addMinutes($currentTime->format('i')) ->lessThan($now->add($delaiPrepa)) ) { $isDispo = false; } // Attention si la date est passée (today mais avant maintenant) : pas de créneau. if ($current > $today || $currentTime >= $now) { // Si un jour de la semaine prochaine est férié et qu'on passe commande un jeudi après 23h59...alors le vendredi de la semaine suivante n'est pas dispo. $nextFriday = $now->addWeek()->startOfWeek(Carbon::MONDAY)->addDays(4); if ($hasPublicHolidayNextWeekButNoFriday && $afterThursdayNight && $current->isSameDay($nextFriday)) { $isDispo = false; } $retArray[] = array($creneau, $isDispo); } $currentTime->add($interval); } while ($currentTime <= $endTime); return $retArray; } /** * give array of available crenaux * * @param integer $id * @return array */ public function getAvailableCreneauxByShop(int $id) { // Setting à stocker et réutiliser. $plage_days = Setting::where('key', 'order_withdrawal_period')->firstOrFail()->value; $magasin = Magasin::findOrFail($id); $timeslotSettings = $magasin->timeslotSettings; // $timeslotSettings = TimeslotSetting::all(); // Fabriquons un tableau recap. $plage = array(); foreach ($timeslotSettings as $day) { $plage[$day->day_index] = array( 'morning_start' => $day->morning_start, 'morning_end' => $day->morning_end, 'afternoon_start' => $day->afternoon_start, 'afternoon_end' => $day->afternoon_end, 'interval' => $day->interval, 'min_order_preparation' => $day->min_order_preparation, 'max_orders_per_interval' => $day->max_orders_per_interval ); } // Génération des plages. // On commence le calcul à aujourd'hui + 1 // car on ne va pas afficher de créaneaux pour aujourd'hui dans l'interface... $today = CarbonImmutable::now()->addDay()->startOfDay(); $weekDay = $today->weekday(); // Final boundary (permet de limiter la liste des jours d'exception). $finalday = $today->add($plage_days, 'day'); $now = CarbonImmutable::now(); // Jours exceptions. $noCollectDays = $magasin->collecteExceptions() ->whereDate('date', '>', $now) ->whereDate('date', '<=', $finalday) ->orderBy('date')->get(); // On effectue des vérifications pour récupérer des jours de la semaine suivante // =/= de vendredi qui seraient fériés. $thursdayNight = $now->startOfWeek(Carbon::MONDAY)->addDays(3)->setTime(23, 59); $afterThursdayNight = $now->greaterThanOrEqualTo($thursdayNight); $currentweekStartDate = $now->startOfWeek(); $currentweekEndDate = $now->endOfWeek(); $nextWeekStartDate = $currentweekStartDate->addWeek(); $nextWeekEndDate = $currentweekEndDate->addWeek(); $hasPublicHolidayNextWeekButNoFriday = $magasin->collecteExceptions() ->whereDate('date', '>=', $nextWeekStartDate) ->whereDate('date', '<=', $nextWeekEndDate) ->where('type', '=', ExceptionDayType::ferie()) ->whereRaw('DAYOFWEEK(date) != ?', 6) ->count() > 0; $delaiPrepa = CarbonInterval::createFromFormat('h:i', $plage[$weekDay]['min_order_preparation'] ?? '01:00'); $availCreneaux = array(); for ($i = 0; $i < $plage_days; $i++) { $current = $today->add($i, 'day'); /** * On gère le décalage par rapport au tableau de settings... * Dans le tableau lundi = 0 * Dans Carbon dimanche = 0 */ $weekDay = $current->weekday() - 1 !== -1 ? $current->weekday() - 1 : 6; $interval = CarbonInterval::createFromFormat('h:i', $plage[$weekDay]['interval'] ?? '00:15'); // Si c'est un jour exception skip. // ah ? mais on skip quoi et ou ? et pourquoi ? $skip = false; foreach ($noCollectDays as $not_today) { if ($current->isSameDay($not_today->date)) { $skip = true; } } if ($skip) { continue; } $availCreneaux[$current->format('d/m/Y')] = array(); // Matinée. if (!empty($plage[$weekDay]['morning_start'])) { $currentTime = Carbon::createFromTimeString($plage[$weekDay]['morning_start']); $endTime = Carbon::createFromTimeString($plage[$weekDay]['morning_end']); $morningCreneaux = $this->addCreneaux( $plage, $weekDay, $current, $magasin, $now, $today, $delaiPrepa, $interval, $currentTime, $endTime, $hasPublicHolidayNextWeekButNoFriday, $afterThursdayNight, ); // Cela sert à rien d'afficher un jour avec des créneaux sur lesquels le conso ne peut pas cliquer... if ($this->hasCreneauAvailableTime($morningCreneaux)) { $availCreneaux[$current->format('d/m/Y')] = array_merge( $availCreneaux[$current->format('d/m/Y')], $morningCreneaux ); } } //end if // Aprem. if (!empty($plage[$weekDay]['afternoon_start'])) { $currentTime = Carbon::createFromTimeString($plage[$weekDay]['afternoon_start']); $endTime = Carbon::createFromTimeString($plage[$weekDay]['afternoon_end']); $afternoonCreneaux = $this->addCreneaux( $plage, $weekDay, $current, $magasin, $now, $today, $delaiPrepa, $interval, $currentTime, $endTime, $hasPublicHolidayNextWeekButNoFriday, $afterThursdayNight, ); // Cela sert à rien d'afficher un jour avec des créneaux sur lesquels le conso ne peut pas cliquer... if ($this->hasCreneauAvailableTime($afternoonCreneaux)) { $availCreneaux[$current->format('d/m/Y')] = array_merge( $availCreneaux[$current->format('d/m/Y')], $afternoonCreneaux ); } } //end if } //end for return $availCreneaux; } /** * hasCreneauAvailableTime * * @param array $creneaux * * @return boolean */ private function hasCreneauAvailableTime(array $creneaux): bool { foreach ($creneaux as $creneau) { if ($creneau[1]) { return true; } } return false; } /** * Call ajax pour le composant vue de timeslots. * * @param Request $request * @return array */ public function getAvailableCreneaux(Request $request) { return $this->getAvailableCreneauxByShop($request->id); } /** * front * enregistrement du créneau de collect * et validation de la commande * * @param Request $request * @return \Illuminate\Http\RedirectResponse */ public function setTimeSlot(Request $request) { $request->validate([ 'selectedmagasin' => 'required', 'selectedcreneau' => 'required', 'ok_cgv' => 'accepted', ]); $tsday = explode('-', $request->selectedcreneau)[0]; $tstime = explode('-', $request->selectedcreneau)[1]; $magasin_id = $request->selectedmagasin; $reference = $request->order_ref; $order = Order::where('reference', '=', $reference)->firstOrFail(); // Check total price of this customer commands for a specific delivery day. $date = CarbonImmutable::create( explode('/', $tsday)[2], explode('/', $tsday)[1], explode('/', $tsday)[0] ); $start = $date->startOfWeek(Carbon::MONDAY); $end = $date->endOfWeek(Carbon::SUNDAY); $customerOrders = Order::where('customer_id', '=', $order->customer_id) ->where('status_id', '=', 3) ->get(); $totalPrice = 0; foreach ($customerOrders as $key => $customerOrder) { $timeslot_day = $customerOrder->timeslot->day; if ( $timeslot_day >= $start && $timeslot_day <= $end ) { $totalPrice += $customerOrder->total; } } if ($totalPrice > 200) { return redirect()->route('account.order-detail', ['reference' => $order->reference]) ->with('message', 'Le montant total pour cette date de livraison est dépassé (200€ maximum). Veuillez modifier cette dernière pour la valider.'); } if ($order->total > 200) { return redirect()->route('account.order-detail', ['reference' => $order->reference]) ->with('message', 'Le montant total de cette commande est dépassé (200€ maximum). Veuillez modifier cette dernière pour la valider.'); } if ($order->status_id == 1) { // On ne traite l'enregistrement que pour les commandes en attente venant de l'étape de récap. // Save timeslot. $timeslot = Timeslot::firstOrNew(['order_id' => $order->id]); $timeslot->order_id = $order->id; $timeslot->magasin_id = $magasin_id; $timeslot->day = $date; $timeslot->time = $tstime; $timeslot->save(); // Save order status. $order->status_id = 3; $order->save(); // Send confirm mail. Mail::to($order->customer->email)->queue(new OrderConfirmMail($order)); // Envoi du mail de confirmation aux admins site. $toMailSetting = Setting::where('key', 'send_order_confirm_mail_to')->first(); if (empty($toMailSetting->value)) { $toMail = config('mail.from.address'); } else { $toMail = $toMailSetting->value; } Mail::to($toMail)->queue(new OrderConfirmAdminMail($order)); // Export commande. if (config('bosettings.regate_orders')) { Notification::route('mail', config('mail.to.regate'))->notify(new AnOrderWasPlaced($order)); } } //end if // Redirect to final confirmation. return redirect()->route('sales.confirm', ['reference' => $order->reference]); } /** * front * page de confirmation de commande * * @param string $reference * @return \Illuminate\Contracts\View\View */ public function confirmOrder(string $reference) { $activestep = 3; $idcommande = Session::get('idcommande'); $order = Order::where('reference', '=', $reference)->firstOrFail(); // 1 seul magasin en base pour le moment $magasin = Magasin::find($order->timeslot->magasin->id); // Empty cart. Cart::instance('panier')->destroy(); return view('sales.confirm', compact('activestep', 'order', 'magasin')); } }