Browse Source

Merge branch 'producer/feature/nouvelle_page_commande' into dev

refactoring
Guillaume Bourgeois 6 years ago
parent
commit
71510fc4df
11 changed files with 1499 additions and 29 deletions
  1. +5
    -0
      common/config/main.php
  2. +62
    -0
      producer/assets/VuejsOrderOrderAsset.php
  3. +203
    -24
      producer/controllers/OrderController.php
  4. +4
    -4
      producer/views/layouts/main.php
  5. +271
    -0
      producer/views/order/order.php
  6. BIN
      producer/web/.sass-cache/df296f4d5446ec0f190b7ff1f23eb998ea1b56e7/_responsive.scssc
  7. +313
    -1
      producer/web/css/screen.css
  8. +286
    -0
      producer/web/js/vuejs/order-order.js
  9. +62
    -0
      producer/web/sass/_responsive.scss
  10. +292
    -0
      producer/web/sass/order/_order.scss
  11. +1
    -0
      producer/web/sass/screen.scss

+ 5
- 0
common/config/main.php View File

@@ -59,6 +59,11 @@ return [
'httpOnly' => true,
],
],
'request' => [
'parsers' => [
'application/json' => 'yii\web\JsonParser',
]
],
'cache' => [
'class' => 'yii\caching\FileCache',
],

+ 62
- 0
producer/assets/VuejsOrderOrderAsset.php View File

@@ -0,0 +1,62 @@
<?php
/**
Copyright La boîte à pain (2018)

contact@laboiteapain.net

Ce logiciel est un programme informatique servant à aider les producteurs
à distribuer leur production en circuits courts.

Ce logiciel est régi par la licence CeCILL soumise au droit français et
respectant les principes de diffusion des logiciels libres. Vous pouvez
utiliser, modifier et/ou redistribuer ce programme sous les conditions
de la licence CeCILL telle que diffusée par le CEA, le CNRS et l'INRIA
sur le site "http://www.cecill.info".

En contrepartie de l'accessibilité au code source et des droits de copie,
de modification et de redistribution accordés par cette licence, il n'est
offert aux utilisateurs qu'une garantie limitée. Pour les mêmes raisons,
seule une responsabilité restreinte pèse sur l'auteur du programme, le
titulaire des droits patrimoniaux et les concédants successifs.

A cet égard l'attention de l'utilisateur est attirée sur les risques
associés au chargement, à l'utilisation, à la modification et/ou au
développement et à la reproduction du logiciel par l'utilisateur étant
donné sa spécificité de logiciel libre, qui peut le rendre complexe à
manipuler et qui le réserve donc à des développeurs et des professionnels
avertis possédant des connaissances informatiques approfondies. Les
utilisateurs sont donc invités à charger et tester l'adéquation du
logiciel à leurs besoins dans des conditions permettant d'assurer la
sécurité de leurs systèmes et ou de leurs données et, plus généralement,
à l'utiliser et l'exploiter dans les mêmes conditions de sécurité.

Le fait que vous puissiez accéder à cet en-tête signifie que vous avez
pris connaissance de la licence CeCILL, et que vous en avez accepté les
termes.
*/

namespace producer\assets;

use yii\web\AssetBundle;
use yii ;

/**
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class VuejsOrderOrderAsset extends \common\components\MyAssetBundle
{
public $basePath = '@webroot';
public $baseUrl = '@web';
public $css = [];
public $js = [];
public $depends = [
'common\assets\CommonAsset'
];
public function __construct()
{
parent::__construct() ;
$this->addAsset('js','js/vuejs/order-order.js') ;
}
}

+ 203
- 24
producer/controllers/OrderController.php View File

@@ -39,9 +39,14 @@ termes.
namespace producer\controllers;

use common\models\ProductDistribution ;
use common\models\User ;
use common\models\Producer ;
use common\models\Order ;
use DateTime;

class OrderController extends ProducerBaseController
{
var $enableCsrfValidation = false;

public function behaviors()
{
@@ -57,6 +62,11 @@ class OrderController extends ProducerBaseController
],
];
}
public function actionOrder()
{
return $this->render('order') ;
}

/**
* Retourne au format JSON toutes les informations relatives à une
@@ -130,7 +140,9 @@ class OrderController extends ProducerBaseController
$deadline = 20;
$date = date('Y-m-d');
if (isset($producer)) {
$deadline = $producer->order_deadline;
if($producer->order_deadline) {
$deadline = $producer->order_deadline;
}
if (date('H') >= $deadline) {
$date = date('Y-m-d', strtotime(date('Y-m-d')) + ($producer->order_delay) * (24 * 60 * 60));
} else {
@@ -269,22 +281,26 @@ class OrderController extends ProducerBaseController
*
* @return mixed
*/
public function actionCreate()
public function actionAjaxProcess()
{
\Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
$order = new Order ;
$idProducer = $this->getProducer()->id ;
$order = new Order;

$posts = Yii::$app->request->post();
if ($idProducer) {
$this->_verifyProducerActive($idProducer);
}

if ($order->load($posts)) {

$order = Order::find()
->where('id_distribution = ' . $posts['Order']['id_distribution'])
->andWhere('id_user = ' . User::getCurrentId())
->where('id_distribution = :id_distribution')
->andWhere('id_user = :id_user')
->params([
':id_distribution' => $posts['Order']['id_distribution'],
':id_user' => User::getCurrentId()
])
->one();
if (!$order) {
@@ -295,12 +311,14 @@ class OrderController extends ProducerBaseController
$order->origin = Order::ORIGIN_USER;
}
$this->processForm($order);
$errors = $this->processForm($order);
if(count($errors)) {
return ['status' => 'error', 'errors' => $errors] ;
}
}

return $this->render('create', array_merge($this->initForm($order), [
'model' => $order
]));
return ['status' => 'success'] ;
}

/**
@@ -359,9 +377,8 @@ class OrderController extends ProducerBaseController

$totalQuantity = 0;

foreach ($posts['Product'] as $key => $quantity) {
$key = (int) str_replace('product_', '', $key);
$product = Product::find()->where(['id' => $key])->one();
foreach ($posts['products'] as $key => $quantity) {
$product = Product::find()->where(['id' => (int) $key])->one();
$totalQuantity += $quantity;
if ($product && $quantity) {
$productsArray[] = $product;
@@ -400,7 +417,7 @@ class OrderController extends ProducerBaseController
$pointSale = PointSale::findOne($posts['Order']['id_point_sale']);

if ($pointSale) {
if (strlen($pointSale->code) && !$pointSale->validateCode($posts['code_point_sale_' . $pointSale->id])) {
if (strlen($pointSale->code) && !$pointSale->validateCode($posts['code_point_sale'])) {
$errorPointSale = true;
}
} else {
@@ -408,6 +425,8 @@ class OrderController extends ProducerBaseController
}
}

$errors = [] ;
if ($order->validate() && count($productsArray) && !$errorDate && !$errorPointSale) {

// gestion point de vente
@@ -445,7 +464,7 @@ class OrderController extends ProducerBaseController

$productOrder->price = $product->price;

$quantity = (int) $posts['Product']['product_' . $product->id];
$quantity = (int) $posts['products'][$product->id] ;
if ($availableProducts[$product->id]['quantity_max'] && $quantity > $availableProducts[$product->id]['quantity_remaining']) {
$quantity = $availableProducts[$product->id]['quantity_remaining'];
}
@@ -458,7 +477,7 @@ class OrderController extends ProducerBaseController
}

// credit
$credit = isset($posts['credit']) && $posts['credit'];
$credit = Producer::getConfig('credit');
$order = Order::searchOne([
'id' => $order->id
]) ;
@@ -498,20 +517,21 @@ class OrderController extends ProducerBaseController
}

// redirection
$this->redirect(Yii::$app->urlManager->createUrl(['order/history', 'orderOk' => true]));
//$this->redirect(Yii::$app->urlManager->createUrl(['order/history', 'orderOk' => true]));
}
else {
if (!count($productsArray)) {
Yii::$app->session->setFlash('error', "Vous n'avez choisi aucun produit");
$errors[] = "Vous n'avez choisi aucun produit" ;
}
if ($errorDate) {
Yii::$app->session->setFlash('error', "Vous ne pouvez pas commander pour cette date.");
$errors[] = "Vous ne pouvez pas commander pour cette date." ;
}
if ($errorPointSale) {
Yii::$app->session->setFlash('error', "Point de vente invalide.");
$errors[] = "Point de vente invalide." ;
}
}
return $errors ;
}

/**
@@ -563,15 +583,174 @@ class OrderController extends ProducerBaseController
* @param string $code
* @return boolean
*/
public function actionValidateCodePointSale($idPointSale, $code)
public function actionAjaxValidateCodePointSale($idPointSale, $code)
{
\Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
$pointSale = PointSale::findOne($idPointSale);
if ($pointSale) {
if ($pointSale->validateCode($code)) {
return true;
return 1;
}
}
return 0;
}
public function actionAjaxInfos($date = '')
{
\Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
$json = [] ;
$format = 'Y-m-d' ;
$dateObject = DateTime::createFromFormat($format, $date);
// Producteur
$producer = Producer::searchOne([
'id' => $this->getProducer()->id
]) ;
$json['producer'] = [
'order_infos' => $producer->order_infos,
'credit' => $producer->credit
] ;
// Distributions
$deadline = 20;
$dateMini = date('Y-m-d');
if (isset($producer)) {
if($producer->order_deadline) {
$deadline = $producer->order_deadline;
}
if (date('H') >= $deadline) {
$dateMini = date('Y-m-d', strtotime(date('Y-m-d')) + ($producer->order_delay) * (24 * 60 * 60));
} else {
$dateMini = date('Y-m-d', strtotime(date('Y-m-d')) + ($producer->order_delay - 1) * (24 * 60 * 60));
}
}
$distributionsArray = Distribution::searchAll([
'active' => 1
], [
'conditions' => ['date > :date'],
'params' => [':date' => $dateMini],
]) ;
$json['distributions'] = $distributionsArray ;
// Commandes de l'utilisateur
$ordersUserArray = Order::searchAll([
'id_user' => User::getCurrentId()
], [
'conditions' => [
'distribution.date > :date'
],
'params' => [
':date' => $dateMini
]
]);
foreach($ordersUserArray as &$order) {
$order = array_merge($order->getAttributes(), [
'amount_total' => $order->getAmount(Order::AMOUNT_TOTAL),
'date_distribution' => $order->distribution->date,
'pointSale' => $order->pointSale->getAttributes()
]) ;
}
$json['orders'] = $ordersUserArray;
// User
$userProducer = UserProducer::searchOne([
'id_producer' => $producer->id,
'id_user' => User::getCurrentId()
]) ;
$json['credit'] = $userProducer->credit ;
if($dateObject && $dateObject->format($format) === $date) {
// Commande de l'utilisateur
$orderUser = Order::searchOne([
'distribution.date' => $date,
'id_user' => User::getCurrentId(),
]);
if($orderUser) {
$json['order'] = array_merge($orderUser->getAttributes(), [
'amount_total' => $orderUser->getAmount(Order::AMOUNT_TOTAL),
'amount_paid' => $orderUser->getAmount(Order::AMOUNT_PAID),
]) ;
}
// distribution
$distribution = Distribution::initDistribution($date) ;
$json['distribution'] = $distribution ;
$pointsSaleArray = PointSale::find()
->joinWith(['pointSaleDistribution' => function($query) use ($distribution) {
$query->where(['id_distribution' => $distribution->id]);
}
])
->with(['userPointSale' => function($query) {
$query->onCondition(['id_user' => User::getCurrentId()]) ;
}])
->where([
'id_producer' => $distribution->id_producer,
])
->all();

foreach($pointsSaleArray as &$pointSale) {
$pointSale = array_merge($pointSale->getAttributes(),[
'pointSaleDistribution' => [
'id_distribution' => $pointSale->pointSaleDistribution[0]->id_distribution,
'id_point_sale' => $pointSale->pointSaleDistribution[0]->id_point_sale,
'delivery' => $pointSale->pointSaleDistribution[0]->delivery
],
'userPointSale' => ($pointSale->userPointSale ? $pointSale->userPointSale[0] : '')
]) ;
}
$json['points_sale'] = $pointsSaleArray;
// Commandes totales
$ordersArray = Order::searchAll([
'distribution.date' => $date,
]);
// Produits
$productsArray = Product::find()
->where([
'id_producer' => $this->getProducer()->id,
])
->joinWith(['productDistribution' => function($query) use($distribution) {
$query->andOnCondition('product_distribution.id_distribution = '.$distribution->id) ;
}])
->orderBy('product_distribution.active DESC, order ASC')
->asArray()
->all();
$indexProduct = 0 ;
foreach($productsArray as &$product) {
$quantityOrder = Order::getProductQuantity($product['id'], $ordersArray) ;
$product['quantity_ordered'] = $quantityOrder ;
$product['quantity_remaining'] = $product['quantity_max'] - $quantityOrder ;
if($orderUser) {
$quantityOrderUser = Order::getProductQuantity($product['id'], [$orderUser]) ;
$product['quantity_ordered'] = $quantityOrder ;
$product['quantity_remaining'] = $product['quantity_max'] - $quantityOrder + $quantityOrderUser ;
$product['quantity_form'] = $quantityOrderUser ;
}
else {
$product['quantity_form'] = 0 ;
}
if($product['quantity_remaining'] < 0) $product['quantity_remaining'] = 0 ;
$product['index'] = $indexProduct ++ ;
}
$json['products'] = $productsArray;
}
return false;
return $json ;
}

}

+ 4
- 4
producer/views/layouts/main.php View File

@@ -155,15 +155,15 @@ $producer = $this->context->getProducer() ;
],
[
'label' => '<span class="glyphicon glyphicon-plus"></span> Commander',
'url' => Yii::$app->urlManager->createUrl(['order/create']),
'url' => Yii::$app->urlManager->createUrl(['order/order']),
'visible' => !Yii::$app->user->isGuest,
'active' => $this->getControllerAction() == 'order/create' || $this->getControllerAction() == 'order/update',
'active' => $this->getControllerAction() == 'order/order',
],
[
'label' => '<span class="glyphicon glyphicon-plus"></span> Commander',
'url' => Yii::$app->urlManagerFrontend->createAbsoluteUrl(['site/producer','id' => $this->context->getProducer()->id,'return_url' => Yii::$app->urlManagerProducer->createAbsoluteUrl(['order/create','slug_producer' => $this->context->getProducer()->slug])]),
'url' => Yii::$app->urlManagerFrontend->createAbsoluteUrl(['site/producer','id' => $this->context->getProducer()->id,'return_url' => Yii::$app->urlManagerProducer->createAbsoluteUrl(['order/order','slug_producer' => $this->context->getProducer()->slug])]),
'visible' => Yii::$app->user->isGuest,
'active' => $this->getControllerAction() == 'order/create' || $this->getControllerAction() == 'order/update',
'active' => $this->getControllerAction() == 'order/order',
],
[
'label' => '<span class="glyphicon glyphicon-folder-open"></span> Historique',

+ 271
- 0
producer/views/order/order.php View File

@@ -0,0 +1,271 @@
<?php

/**
Copyright La boîte à pain (2018)

contact@laboiteapain.net

Ce logiciel est un programme informatique servant à aider les producteurs
à distribuer leur production en circuits courts.

Ce logiciel est régi par la licence CeCILL soumise au droit français et
respectant les principes de diffusion des logiciels libres. Vous pouvez
utiliser, modifier et/ou redistribuer ce programme sous les conditions
de la licence CeCILL telle que diffusée par le CEA, le CNRS et l'INRIA
sur le site "http://www.cecill.info".

En contrepartie de l'accessibilité au code source et des droits de copie,
de modification et de redistribution accordés par cette licence, il n'est
offert aux utilisateurs qu'une garantie limitée. Pour les mêmes raisons,
seule une responsabilité restreinte pèse sur l'auteur du programme, le
titulaire des droits patrimoniaux et les concédants successifs.

A cet égard l'attention de l'utilisateur est attirée sur les risques
associés au chargement, à l'utilisation, à la modification et/ou au
développement et à la reproduction du logiciel par l'utilisateur étant
donné sa spécificité de logiciel libre, qui peut le rendre complexe à
manipuler et qui le réserve donc à des développeurs et des professionnels
avertis possédant des connaissances informatiques approfondies. Les
utilisateurs sont donc invités à charger et tester l'adéquation du
logiciel à leurs besoins dans des conditions permettant d'assurer la
sécurité de leurs systèmes et ou de leurs données et, plus généralement,
à l'utiliser et l'exploiter dans les mêmes conditions de sécurité.

Le fait que vous puissiez accéder à cet en-tête signifie que vous avez
pris connaissance de la licence CeCILL, et que vous en avez accepté les
termes.
*/

\producer\assets\VuejsOrderOrderAsset::register($this);

$this->setTitle('Commander') ;

?>

<div id="app-order-order" :class="{'loaded': !loadingInit}">
<div v-if="orderSuccess" id="order-success">
<div class="alert alert-success">
<span class="glyphicon glyphicon-ok glyphicon-big"></span>
<div class="content">
<h3>Votre commande a bien été prise en compte</h3>
<a href="<?= Yii::$app->urlManagerProducer->createUrl(['order/history']) ?>" class="btn btn-default">
<span class="glyphicon glyphicon-chevron-right"></span>
Voir toutes mes commandes
</a>
</div>
<div class="clr"></div>
</div>
<div class="alert alert-info">
<span class="glyphicon glyphicon-list-alt glyphicon-big"></span>
<div class="content">
<h3>Récapitulatif de votre commande</h3>
<ul>
<li><span class="glyphicon glyphicon-time"></span> {{ dateFormat }}</li>
<li><span class="glyphicon glyphicon-map-marker"></span> {{ pointSaleActive.name }} <span class="locality" v-if="pointSaleActive.locality.length > 0">à {{ pointSaleActive.locality }}</span></li>
<li><span class="glyphicon glyphicon-th-list"></span> {{ countProductOrdered() }} produits</li>
<li><span class="glyphicon glyphicon-chevron-right"></span> {{ priceTotal(true) }}</li>
</ul>
</div>
<div class="clr"></div>
</div>
</div>
<div v-else>
<div :class="(producer != null && producer.order_infos.length) ? 'col-md-9' : 'col-md-12'">
<div id="steps">
<ul>
<li id="step-date" :class="'col-md-3 '+((step == 'date') ? 'active' : '')">
<div class="info-step" v-if="dateFormat">
{{ dateFormat }}
</div>
<button @click="changeStep('date')" :class="'btn '+ (step == 'date' ? 'btn-primary' : 'btn-default')">
<span class="button-content"><span class="glyphicon glyphicon-time"></span> Date</span></span>
</button>
</li>
<li id="step-point-sale" :class="'col-md-3 '+((step == 'point-sale') ? 'active ' : '')">
<div class="info-step" v-if="pointSaleActive">
{{ pointSaleActive.name }}
</div>
<button @click="changeStep('point-sale')" :class="'btn '+ (step == 'point-sale' ? 'btn-primary' : 'btn-default')" :disabled="step == 'date'">
<span class="button-content"><span class="glyphicon glyphicon-map-marker"></span> Points de vente</span>
</button>
</li>
<li id="step-products" :class="'col-md-3 '+((step == 'products') ? 'active ' : '')">
<div class="info-step" v-if="oneProductOrdered()">
{{ countProductOrdered() }} produit{{ (countProductOrdered() > 1) ? 's' : '' }}
</div>
<button @click="changeStep('products')" :class="'btn '+ (step == 'products' ? 'btn-primary' : 'btn-default')" :disabled="step == 'date' || step == 'point-sale'">
<span class="button-content"><span class="glyphicon glyphicon-th-list"></span> Produits</span>
</button>
</li>
<li id="step-payment" :class="'col-md-3 '+((step == 'payment') ? 'active' : '')">
<button @click="changeStep('payment')" :class="'btn '+ (step == 'payment' ? 'btn-primary' : 'btn-default')" :disabled="step == 'date' || step == 'point-sale' || step == 'products'">
<span class="button-content"><span class="glyphicon glyphicon-ok"></span> Confirmation</span>
</button>
</li>
</ul>
<div class="clr"></div>
</div>
<div class="content">
<transition name="slide">
<div id="content-step-date" v-if="step == 'date'">
<div id="legend">
<div><span id="distribution-date-color"></span> Prochains jours de distribution</div>
<div><span id="order-date-color"></span> Vos commandes déjà enregistrées</div>
</div>
<div id="calendar">
<v-calendar
is-inline
is-double-paned
is-expanded
v-model="date"
mode="single"
:formats="calendar.formats"
:theme-styles="calendar.themeStyles"
:attributes="calendar.attrs"
:available-dates="calendar.availableDates"
@dayclick='dayClick'>
></v-calendar>
</div>
</div>
</transition>
<transition name="slide">
<div id="content-step-point-sale" v-if="step == 'point-sale'">
<div v-if="loading">
Chargement ...
</div>
<div v-else>
<table id="points-sale" class="table table-bordered" v-if="pointsSale.length">
<thead>
<tr>
<th>Nom</th>
<th>Localité</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="pointSale in pointsSale" v-if="pointSale && pointSale.pointSaleDistribution.delivery" :class="(pointSaleActive && pointSale.id == pointSaleActive.id) ? 'selected' : ''">
<td class="name">
<span class="the-name">{{ pointSale.name }}</span>
<div class="comment" v-if="pointSale.userPointSale">
{{ pointSale.userPointSale.comment }}
</div>
</td>
<td class="locality">{{ pointSale.locality }}</td>
<td class="actions">
<div :class="'form-group' + (pointSale.invalid_code ? ' has-error' : '')">
<div class="input-group" v-if="pointSale.code.length > 0">
<span class="input-group-addon"><span class="glyphicon glyphicon-lock"></span></span>
<input v-model="pointsSaleCodes[pointSale.id]" type="password" placeholder="Code" class="form-control input-code" />
</div>
</div>
<button class="btn btn-primary" @click="pointSaleClick" :data-code="pointSale.code.length > 0" :data-id-point-sale="pointSale.id">
<span class="glyphicon glyphicon-map-marker"></span>
Choisir
</button>
</td>
</tr>
</tbody>
</table>
<div class="alert alert-warning" v-else>
Aucun point de vente disponible pour ce jour de distribution.
</div>
</div>
</div>
</transition>
<transition name="slide">
<div id="content-step-products" v-if="step == 'products'">
<div v-if="products.length">
<table id="products" class="table table-bordered" >
<thead>
<tr>
<th>Nom</th>
<th>Prix unitaire</th>
<th>Quantité</th>
<th>Total</th>
</tr>
</thead>
<tbody>
<tr v-for="product in products" v-if="product.productDistribution[0].active == 1">
<td class="name">
<span class="name">{{ product.name }}</span>
<span class="other">
<span v-if="product.description.length">/</span>
<span class="description">{{ product.description }}</span>
<span v-if="product.weight">({{ product.weight }}g)</span>
</span>
<span v-if="product.quantity_form == product.quantity_remaining && product.quantity_max > 0" class="label label-danger">
Épuisé
</span>
<div class="recipe" v-if="product.recipe.length">{{ product.recipe }}</div>
</td>
<td class="price-unit">
{{ formatPrice(product.price) }}
</td>
<td class="td-quantity">
<div class="input-group">
<span class="input-group-btn">
<button class="btn btn-default btn-moins" type="button" @click="productQuantityClick(product, -1)" :disabled="product.quantity_form == 0"><span class="glyphicon glyphicon-minus"></span></button>
</span>
<input type="text" v-model="product.quantity_form" class="form-control quantity" readonly="readonly" />
<span class="input-group-btn">
<button class="btn btn-default btn-plus" type="button" @click="productQuantityClick(product, 1)" :disabled="product.quantity_form == product.quantity_remaining && product.quantity_max > 0"><span class="glyphicon glyphicon-plus"></span></button>
</span>
</div>
</td>
<td class="price-total">
{{ formatPrice(product.price * product.quantity_form) }}
</td>
</tr>
<tr class="total">
<td colspan="3"></td>
<td class="price-total">{{ priceTotal(true) }}</td>
</tr>
</tbody>
</table>
<div class="block-actions">
<button class="btn btn-primary" @click="changeStep('payment')">Valider</button>
</div>
</div>
<div class="alert alert-warning" v-else>
Aucun produit disponible
</div>
</div>
</transition>
<transition name="slide">
<div id="content-step-payment" v-if="step == 'payment'">
<div class="comment">
<label for="order-comment">Commentaire</label>
<textarea id="order-comment" v-model="comment" class="form-control"></textarea>
</div>
<div class="credit">
<div v-if="producer.credit == 1 && pointSaleActive.credit == 1">
<span class="glyphicon glyphicon-chevron-right"></span> La commande va être réglée via votre Crédit ({{ formatPrice(credit) }}).
<div class="info">
<span v-if="order == null || order.amount_paid == 0">{{ priceTotal(true) }} seront débités</span>
<span v-else-if="order != null && order.amount_paid > 0 && order.amount_paid < priceTotal()">{{ formatPrice(priceTotal() - order.amount_paid) }} seront débités</span>
<span v-else-if="order != null && order.amount_paid > priceTotal()">{{ formatPrice(order.amount_paid - priceTotal()) }} seront remboursés</span>
</div>
</div>
<div v-else>
<span class="glyphicon glyphicon-chevron-right"></span> La commande sera à régler sur place.
</div>
</div>
<div class="block-actions">
<button class="btn btn-primary" @click="confirmClick">Je confirme ma commande</button>
</div>
</div>
</transition>
</div>
</div>
<div id="infos" class="col-md-3" v-if="producer != null && producer.order_infos.length">
<div class="panel panel-default">
<div class="panel-heading">
Informations
</div>
<div class="panel-body">
{{ producer.order_infos }}
</div>
</div>
</div>
</div>
</div>

BIN
producer/web/.sass-cache/df296f4d5446ec0f190b7ff1f23eb998ea1b56e7/_responsive.scssc View File


+ 313
- 1
producer/web/css/screen.css View File

@@ -1170,6 +1170,274 @@ termes.
display: none;
}

/* line 3, ../sass/order/_order.scss */
.order-order #app-order-order {
display: none;
}
/* line 7, ../sass/order/_order.scss */
.order-order #app-order-order.loaded {
display: block;
}
/* line 11, ../sass/order/_order.scss */
.order-order #app-order-order .slide-enter-active {
transition: all .2s ease;
}
/* line 15, ../sass/order/_order.scss */
.order-order #app-order-order .slide-leave-active {
transition: all 0s ease;
}
/* line 19, ../sass/order/_order.scss */
.order-order #app-order-order .slide-enter, .order-order #app-order-order .slide-leave-to {
transform: translateX(10px);
opacity: 0;
}
/* line 24, ../sass/order/_order.scss */
.order-order #app-order-order #steps {
margin-bottom: 20px;
}
/* line 26, ../sass/order/_order.scss */
.order-order #app-order-order #steps ul {
margin-top: 30px;
}
/* line 28, ../sass/order/_order.scss */
.order-order #app-order-order #steps ul li {
text-align: center;
padding-right: 8px;
padding-left: 8px;
position: relative;
}
/* line 34, ../sass/order/_order.scss */
.order-order #app-order-order #steps ul li .info-step {
position: absolute;
top: -30px;
left: 0px;
width: 100%;
text-transform: normal;
}
/* line 42, ../sass/order/_order.scss */
.order-order #app-order-order #steps ul li .glyphicon-chevron-right, .order-order #app-order-order #steps ul li.active .glyphicon-chevron-right {
float: right;
color: gray;
position: relative;
top: 10px;
}
/* line 50, ../sass/order/_order.scss */
.order-order #app-order-order #steps ul li#step-date {
padding-left: 0px;
}
/* line 54, ../sass/order/_order.scss */
.order-order #app-order-order #steps ul li#step-payment {
padding-right: 0px;
}
/* line 58, ../sass/order/_order.scss */
.order-order #app-order-order #steps ul li#step-payment .btn::after, .order-order #app-order-order #steps ul li#step-date .btn::before {
display: none;
}
/* line 63, ../sass/order/_order.scss */
.order-order #app-order-order #steps ul li .btn {
color: #333;
text-transform: uppercase;
display: block;
width: 100%;
position: relative;
background-color: #e0e0e0;
-moz-border-radius: 0px;
-webkit-border-radius: 0px;
border-radius: 0px;
border: 0px none;
text-transform: uppercase;
}
/* line 74, ../sass/order/_order.scss */
.order-order #app-order-order #steps ul li .btn .button-content {
position: relative;
left: 8px;
}
/* line 79, ../sass/order/_order.scss */
.order-order #app-order-order #steps ul li .btn.btn-primary {
background-color: #BB8757;
}
/* line 83, ../sass/order/_order.scss */
.order-order #app-order-order #steps ul li .btn::after, .order-order #app-order-order #steps ul li .btn::before {
content: "";
position: absolute;
top: -1px;
}
/* line 89, ../sass/order/_order.scss */
.order-order #app-order-order #steps ul li .btn::after {
right: -34px;
border: 17px solid transparent;
border-left: 17px solid #e0e0e0;
background-color: transparent;
}
/* line 96, ../sass/order/_order.scss */
.order-order #app-order-order #steps ul li .btn::before {
left: 0px;
border: 17px solid transparent;
border-left: 17px solid white;
background-color: transparent;
}
/* line 103, ../sass/order/_order.scss */
.order-order #app-order-order #steps ul li .btn.btn-primary::after {
border-left: 17px solid #BB8757;
}
/* line 117, ../sass/order/_order.scss */
.order-order #app-order-order #steps ul li .btn-primary {
color: white;
}
/* line 125, ../sass/order/_order.scss */
.order-order #app-order-order #legend #order-date-color,
.order-order #app-order-order #legend #distribution-date-color {
width: 13px;
height: 13px;
display: inline-block;
}
/* line 132, ../sass/order/_order.scss */
.order-order #app-order-order #legend #order-date-color {
background-color: #018548;
}
/* line 135, ../sass/order/_order.scss */
.order-order #app-order-order #legend #distribution-date-color {
background-color: #00A65A;
}
/* line 140, ../sass/order/_order.scss */
.order-order #app-order-order #calendar {
margin-bottom: 15px;
}
/* line 142, ../sass/order/_order.scss */
.order-order #app-order-order #calendar .c-header .c-title-layout .c-title-popover .c-title-anchor .c-title[data-v-2083cb72] {
font-size: 2rem;
}
/* line 145, ../sass/order/_order.scss */
.order-order #app-order-order #calendar .c-day-background {
padding: 20px;
}
/* line 150, ../sass/order/_order.scss */
.order-order #app-order-order #calendar .c-day:hover .c-day-background {
background-color: #F39C12 !important;
}
/* line 155, ../sass/order/_order.scss */
.order-order #app-order-order #calendar .c-day-popover-content {
font-size: 1.3rem;
}
/* line 160, ../sass/order/_order.scss */
.order-order #app-order-order .block-actions {
text-align: right;
margin-top: 20px;
}
/* line 167, ../sass/order/_order.scss */
.order-order #app-order-order table#points-sale td.name .the-name {
text-transform: uppercase;
font-family: "myriadpro-regular";
color: black;
font-size: 16px;
}
/* line 175, ../sass/order/_order.scss */
.order-order #app-order-order table#points-sale td.actions {
width: 150px;
}
/* line 177, ../sass/order/_order.scss */
.order-order #app-order-order table#points-sale td.actions button {
width: 100%;
}
/* line 183, ../sass/order/_order.scss */
.order-order #app-order-order table#points-sale tr.selected td {
background-color: #F8F1DD;
}
/* line 191, ../sass/order/_order.scss */
.order-order #app-order-order table#products td.name .name {
text-transform: uppercase;
font-family: "myriadpro-regular";
color: black;
font-size: 16px;
}
/* line 197, ../sass/order/_order.scss */
.order-order #app-order-order table#products td.name .other {
font-size: 14px;
color: #333;
}
/* line 201, ../sass/order/_order.scss */
.order-order #app-order-order table#products td.name .recipe {
color: gray;
}
/* line 205, ../sass/order/_order.scss */
.order-order #app-order-order table#products .price-unit, .order-order #app-order-order table#products .price-total {
width: 100px;
text-align: center;
}
/* line 209, ../sass/order/_order.scss */
.order-order #app-order-order table#products .td-quantity {
width: 150px;
}
/* line 211, ../sass/order/_order.scss */
.order-order #app-order-order table#products .td-quantity input.quantity {
text-align: center;
}
/* line 217, ../sass/order/_order.scss */
.order-order #app-order-order table#products tr.total .price-total {
font-size: 23px;
}
/* line 224, ../sass/order/_order.scss */
.order-order #app-order-order #content-step-payment .credit {
margin-top: 20px;
}
/* line 227, ../sass/order/_order.scss */
.order-order #app-order-order #content-step-payment .credit .info {
margin-left: 20px;
color: gray;
}
/* line 234, ../sass/order/_order.scss */
.order-order #app-order-order #infos {
margin-top: 30px;
}
/* line 236, ../sass/order/_order.scss */
.order-order #app-order-order #infos .panel-body {
padding-top: 0px;
white-space: pre-line;
}
/* line 244, ../sass/order/_order.scss */
.order-order #app-order-order #order-success .alert.alert-success .glyphicon-big {
background-color: #00A65A;
}
/* line 250, ../sass/order/_order.scss */
.order-order #app-order-order #order-success .alert.alert-info .glyphicon-big {
background-color: #0097BC;
padding: 55px 30px;
}
/* line 256, ../sass/order/_order.scss */
.order-order #app-order-order #order-success .alert {
padding: 0px;
}
/* line 258, ../sass/order/_order.scss */
.order-order #app-order-order #order-success .alert .glyphicon-big {
font-size: 90px;
color: white;
padding: 30px;
float: left;
}
/* line 265, ../sass/order/_order.scss */
.order-order #app-order-order #order-success .alert div.content {
color: #333;
padding: 20px;
margin-left: 151px;
}
/* line 270, ../sass/order/_order.scss */
.order-order #app-order-order #order-success .alert div.content h3 {
font-family: "myriadpro-light";
text-transform: uppercase;
font-size: 30px;
color: #333;
margin-bottom: 20px;
margin-top: 0px;
margin-left: 0px;
text-align: left;
padding-left: 0px;
line-height: 35px;
}
/* line 283, ../sass/order/_order.scss */
.order-order #app-order-order #order-success .alert div.content .locality {
color: gray;
}

/**
Copyright La boîte à pain (2018)

@@ -1305,7 +1573,51 @@ termes.
width: 100%;
}

/* line 145, ../sass/_responsive.scss */
/* line 148, ../sass/_responsive.scss */
.order-order #app-order-order #steps ul li {
padding-left: 0px;
padding-right: 0px;
}
/* line 152, ../sass/_responsive.scss */
.order-order #app-order-order #steps ul li .info-step {
display: none;
}
/* line 156, ../sass/_responsive.scss */
.order-order #app-order-order #steps ul li .btn::after,
.order-order #app-order-order #steps ul li .btn::before {
display: none;
}
/* line 167, ../sass/_responsive.scss */
.order-order #app-order-order table#products td.name .recipe {
display: none;
}
/* line 173, ../sass/_responsive.scss */
.order-order #app-order-order table#products td.td-quantity .input-group-btn {
width: 100%;
display: block;
}
/* line 177, ../sass/_responsive.scss */
.order-order #app-order-order table#products td.td-quantity .input-group-btn button {
width: 100%;
display: block;
}
/* line 187, ../sass/_responsive.scss */
.order-order #app-order-order #order-success .alert .glyphicon-big {
font-size: 90px;
color: white;
padding: 30px;
display: block;
width: 100%;
text-align: center;
margin-bottom: 20px;
}
/* line 196, ../sass/_responsive.scss */
.order-order #app-order-order #order-success .alert div.content {
margin-left: 0px;
text-align: center;
}

/* line 207, ../sass/_responsive.scss */
#footer .content {
text-align: center;
}

+ 286
- 0
producer/web/js/vuejs/order-order.js View File

@@ -0,0 +1,286 @@

var app = new Vue({
el: '#app-order-order',
data: {
loading: false,
loadingInit: true,
step: 'date',
producer: null,
date: null,
dateFormat: null,
distribution: null,
pointsSale: [],
pointSaleActive: null,
pointsSaleCodes: [],
products: [],
comment: '',
creditCheckbox: false,
credit: 0,
orderSuccess: false,
calendar: {
mode: 'single',
attrs: [],
availableDates: [],
themeStyles: {
wrapper: {
background: '#BB8757',
color: '#fafafa',
},
header: {
padding: '10px 10px',
},
headerHorizontalDivider: {
borderTop: 'solid rgba(255, 255, 255, 0.2) 1px',
width: '80%',
},
weekdays: {
color: 'white',
fontWeight: '600',
padding: '10px 10px',
fontSize: '2rem'
},
weeks: {
padding: '0 15px 15px 15px',
},
dayContent: function(object) {
var style = {
fontSize: '1.5rem',
padding: '20px',
};
return style ;
},
},
formats: {
dayPopover: 'DD/MM/YYYY'
}
},
},
mounted: function() {
if($('#distribution-date').size()) {
this.date = new Date($('#distribution-date').html()) ;
this.dateFormat = ('0' + this.date.getDate()).slice(-2)+ '/'
+ ('0' + (this.date.getMonth() +1)).slice(-2) + '/'
+ this.date.getFullYear() ;
}
this.init() ;
this.loadingInit = false ;
},
methods: {
getDate: function() {
return this.formatDate(this.date) ;
},
formatDate: function(date) {
if(date) {
return date.getFullYear() + '-'
+ ('0' + (date.getMonth() +1)).slice(-2) + '-'
+ ('0' + date.getDate()).slice(-2) ;
}
return false ;
},
formatPrice: function(price) {
var isNumberRegExp = new RegExp(/^[-+]?[0-9]+(\.[0-9]+)*$/);
if(isNumberRegExp.test(price) && price > 0) {
return Number(price).toFixed(2).replace('.',',')+' €' ;
}
return '--' ;
},
getPointSale: function(idPointSale) {
for(var key in this.pointsSale) {
if(this.pointsSale[key].id == idPointSale) {
return this.pointsSale[key] ;
}
}
},
init: function() {
this.loading = true ;
axios.get("ajax-infos",{params: {date : this.getDate()}})
.then(response => {
this.producer = response.data.producer ;
this.credit = response.data.credit ;
this.calendar.attrs = [] ;
this.calendar.availableDates = [] ;
var distributions = response.data.distributions ;
if(distributions.length) {
var arrayDate ;
for(var i= 0; i < distributions.length; i++) {
this.calendar.attrs.push({
highlight: {
backgroundColor: '#00A65A'
},
dates: distributions[i].date,
}) ;
arrayDate = distributions[i].date.split('-') ;
this.calendar.availableDates.push({
start: new Date(arrayDate[0], arrayDate[1] - 1, arrayDate[2]),
end: new Date(arrayDate[0], arrayDate[1] - 1, arrayDate[2])
}) ;
}
}
var orders = response.data.orders ;
if(orders.length) {
for(var i= 0; i < orders.length; i++) {
this.calendar.attrs.push({
highlight: {
backgroundColor: '#018548',
},
popover: {
label: orders[i].pointSale.name + ' / '+this.formatPrice(orders[i].amount_total),
hideIndicator: true
},
dates: orders[i].date_distribution,
}) ;
}
}
if(response.data.distribution) {
this.distribution = response.data.distribution ;
}
if(response.data.points_sale) {
this.pointsSale = [] ;
for(var key in response.data.points_sale) {
this.pointsSale[response.data.points_sale[key].id] = response.data.points_sale[key] ;
this.pointsSaleCodes[response.data.points_sale[key].id] = '' ;
Vue.set(this.pointsSaleCodes, response.data.points_sale[key].id, '');
}
}
if(response.data.products) {
this.products = response.data.products ;
}
this.order = null ;
if(response.data.order) {
this.order = response.data.order ;
this.pointSaleActive = this.getPointSale(response.data.order.id_point_sale) ;
}
else {
this.pointSaleActive = null ;
}
this.loading = false ;
});
},
changeStep: function(step) {
var oldStep = this.step ;
this.step = step ;
window.scroll(0, $('#page-title').position().top - 25) ;
if(oldStep == 'date' && step == 'point-sale') {
this.init() ;
}
},
dayClick: function(day) {
if(this.isAvailableDate(day.date)) {
this.date = day.date ;
this.dateFormat = ('0' + this.date.getDate()).slice(-2)+ '/'
+ ('0' + (this.date.getMonth() +1)).slice(-2) + '/'
+ this.date.getFullYear() ;
this.init() ;
this.changeStep('point-sale') ;
}
},
isAvailableDate: function(date) {
for(var key in this.calendar.availableDates) {
if(date.getTime() == this.calendar.availableDates[key].start.getTime()) {
return true ;
}
}
return false ;
},
pointSaleClick: function(event) {
var idPointSale = event.currentTarget.getAttribute('data-id-point-sale') ;
var hasCode = event.currentTarget.getAttribute('data-code') ;
if(hasCode) {
axios.get('ajax-validate-code-point-sale',{params: {
idPointSale: idPointSale,
code: this.pointsSaleCodes[idPointSale]
}}).then(response => {
if(response.data) {
this.pointsSale[idPointSale].invalid_code = false ;
this.validatePointSale(idPointSale) ;
}
else {
this.pointsSale[idPointSale].invalid_code = true ;
Vue.set(this.pointsSaleCodes, idPointSale, '');
}
}) ;
}
else {
this.validatePointSale(idPointSale) ;
}
},

validatePointSale: function(idPointSale) {
this.pointSaleActive = this.getPointSale(idPointSale) ;
this.changeStep('products') ;
},
productQuantityClick: function(product, quantity) {
if( this.products[product.index].quantity_form + quantity >= 0 &&
(this.products[product.index].quantity_form + quantity <= this.products[product.index].quantity_remaining ||
!this.products[product.index].quantity_max)
) {
this.products[product.index].quantity_form += quantity ;
}
},
oneProductOrdered: function() {
for(var key in this.products) {
if(this.products[key].quantity_form > 0) {
return true ;
}
}
return false ;
},
countProductOrdered: function() {
var count = 0 ;
for(var key in this.products) {
if(this.products[key].quantity_form > 0) {
count += this.products[key].quantity_form ;
}
}
return count ;
},
priceTotal: function(format) {
var price = 0 ;
for(var key in this.products) {
if(this.products[key].quantity_form > 0) {
price += this.products[key].quantity_form * this.products[key].price ;
}
}
if(format) {
return this.formatPrice(price) ;
}
else {
return price ;
}
},
confirmClick: function() {
var productsArray = {} ;
for(var key in this.products) {
if( this.products[key].quantity_form != null &&
this.products[key].quantity_form > 0) {
productsArray[this.products[key].id] = this.products[key].quantity_form ;
}
}
axios.post('ajax-process', {
Order: {
id_distribution : this.distribution.id,
id_point_sale: this.pointSaleActive.id,
comment: this.comment
},
code_point_sale: this.pointsSaleCodes[this.pointSaleActive.id],
products: productsArray
}).then(response => {
if(response.data.status == 'success') {
this.orderSuccess = true ;
}
});
}
}
});

+ 62
- 0
producer/web/sass/_responsive.scss View File

@@ -141,6 +141,68 @@ termes.
width: 100% ;
}
.order-order {
#app-order-order {
#steps {
ul {
li {
padding-left: 0px ;
padding-right: 0px ;
.info-step {
display: none ;
}
.btn::after,
.btn::before {
display: none ;
}
}
}
}
table#products {
td.name {
.recipe {
display: none ;
}
}
td.td-quantity {
.input-group-btn {
width: 100% ;
display: block ;
button {
width: 100% ;
display: block ;
}
}
}
}
#order-success {
.alert {
.glyphicon-big {
font-size: 90px ;
color: white ;
padding: 30px ;
display: block ;
width: 100% ;
text-align: center ;
margin-bottom: 20px ;
}
div.content {
margin-left: 0px ;
text-align: center ;
}
}
}
}
}
#footer {
.content {
text-align: center ;

+ 292
- 0
producer/web/sass/order/_order.scss View File

@@ -0,0 +1,292 @@

.order-order {
#app-order-order {
display: none ;
&.loaded {
display: block ;
}
.slide-enter-active {
transition: all .2s ease;
}
.slide-leave-active {
//transition: all .2s cubic-bezier(1.0, 0.5, 0.8, 1.0);
transition: all 0s ease;
}
.slide-enter, .slide-leave-to {
transform: translateX(10px);
opacity: 0;
}
#steps {
margin-bottom: 20px ;
ul {
margin-top: 30px ;
li {
text-align: center ;
padding-right: 8px ;
padding-left: 8px ;
position: relative ;
.info-step {
position: absolute ;
top: -30px ;
left: 0px ;
width: 100% ;
text-transform: normal ;
}
.glyphicon-chevron-right,
&.active .glyphicon-chevron-right {
float: right ;
color: gray ;
position: relative ;
top: 10px ;
}
&#step-date {
padding-left: 0px ;
}
&#step-payment {
padding-right: 0px ;
}
&#step-payment .btn::after,
&#step-date .btn::before {
display: none ;
}
.btn {
color: #333 ;
text-transform: uppercase ;
display: block ;
width: 100% ;
position: relative ;
background-color: #e0e0e0 ;
@include border-radius(0px) ;
border: 0px none ;
text-transform: uppercase ;
.button-content {
position: relative;
left: 8px ;
}
&.btn-primary {
background-color: $color1 ;
}
&::after, &::before {
content: '' ;
position: absolute ;
top: -1px ;
}
&::after {
right: -34px ;
border:17px solid transparent;
border-left:17px solid #e0e0e0;
background-color: transparent ;
}
&::before {
left: 0px ;
border:17px solid transparent;
border-left:17px solid white;
background-color: transparent ;
}
&.btn-primary::after {
border-left:17px solid $color1;
}
&:hover {
//background-color: lighten($color1, 5) ;
}
&:hover::after {
//border:17px solid transparent;
//border-left:17px solid #fefefe;
}
}
.btn-primary {
color: white ;
}
}
}
}
#legend {
#order-date-color,
#distribution-date-color {
width: 13px ;
height: 13px ;
display: inline-block ;
}
#order-date-color {
background-color: #018548 ;
}
#distribution-date-color {
background-color: #00A65A ;
}
}
#calendar {
margin-bottom: 15px ;
.c-header .c-title-layout .c-title-popover .c-title-anchor .c-title[data-v-2083cb72] {
font-size: 2rem ;
}
.c-day-background {
padding: 20px ;
}
.c-day:hover {
.c-day-background {
background-color: #F39C12 !important ;
}
}
.c-day-popover-content {
font-size: 1.3rem ;
}
}
.block-actions {
text-align: right ;
margin-top: 20px ;
}
table#points-sale {
td.name {
.the-name {
text-transform: uppercase ;
font-family: 'myriadpro-regular' ;
color: black ;
font-size: 16px ;
}
}
td.actions {
width: 150px ;
button {
width: 100% ;
}
}
tr.selected {
td {
background-color: $color2 ;
}
}
}
table#products {
td.name {
.name {
text-transform: uppercase ;
font-family: 'myriadpro-regular' ;
color: black ;
font-size: 16px ;
}
.other {
font-size: 14px ;
color: #333 ;
}
.recipe {
color: gray ;
}
}
.price-unit, .price-total {
width: 100px ;
text-align: center ;
}
.td-quantity {
width: 150px ;
input.quantity {
text-align: center ;
}
}
tr.total {
.price-total {
font-size: 23px ;
}
}
}
#content-step-payment {
.credit {
margin-top: 20px ;
.info {
margin-left: 20px ;
color: gray ;
}
}
}
#infos {
margin-top: 30px ;
.panel-body {
padding-top: 0px ;
white-space: pre-line ;
}
}
#order-success {
.alert.alert-success {
.glyphicon-big {
background-color: #00A65A ;
}
}
.alert.alert-info {
.glyphicon-big {
background-color: #0097BC ;
padding: 55px 30px ;
}
}
.alert {
padding: 0px ;
.glyphicon-big {
font-size: 90px ;
color: white ;
padding: 30px ;
float: left ;
}

div.content {
color: #333 ;
padding: 20px ;
margin-left: 151px ;
h3 {
font-family: 'myriadpro-light' ;
text-transform: uppercase ;
font-size: 30px ;
color: #333 ;
margin-bottom: 20px ;
margin-top: 0px ;
margin-left: 0px ;
text-align: left ;
padding-left: 0px ;
line-height: 35px ;
}
.locality {
color: gray ;
}
}
}
}
}
}

+ 1
- 0
producer/web/sass/screen.scss View File

@@ -41,4 +41,5 @@ termes.
@import "site/_credit_history.scss";
@import "order/_form.scss";
@import "order/_history.scss";
@import "order/_order.scss";
@import "_responsive.scss";

Loading…
Cancel
Save