@@ -161,6 +161,23 @@ class ProductCategoryController extends BackendController | |||
return $this->redirect(['index']); | |||
} | |||
/** | |||
* Modifie l'ordre des catégories. | |||
* | |||
* @param array $array | |||
*/ | |||
public function actionPosition() | |||
{ | |||
$array = Yii::$app->request->post('array'); | |||
$positionArray = json_decode(stripslashes($array)); | |||
foreach ($positionArray as $id => $position) { | |||
$category = $this->findModel($id); | |||
$category->position = $position; | |||
$category->save(); | |||
} | |||
} | |||
/** | |||
* Recherche une catégorie en fonction de son ID. | |||
* |
@@ -1,75 +1,84 @@ | |||
<?php | |||
/** | |||
Copyright distrib (2018) | |||
contact@opendistrib.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. | |||
*/ | |||
/** | |||
* Copyright distrib (2018) | |||
* | |||
* contact@opendistrib.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. | |||
*/ | |||
use yii\helpers\Html; | |||
use yii\grid\GridView; | |||
use common\models\PointVenteUser ; | |||
use common\models\PointVenteUser; | |||
$this->setTitle('Catégories') ; | |||
$this->addBreadcrumb($this->getTitle()) ; | |||
$this->addButton(['label' => 'Nouvelle catégorie <span class="glyphicon glyphicon-plus"></span>', 'url' => 'product-category/create', 'class' => 'btn btn-primary']) ; | |||
$this->setTitle('Catégories'); | |||
$this->addBreadcrumb($this->getTitle()); | |||
$this->addButton(['label' => 'Nouvelle catégorie <span class="glyphicon glyphicon-plus"></span>', 'url' => 'product-category/create', 'class' => 'btn btn-primary']); | |||
?> | |||
<div class="product-category-index"> | |||
<?= GridView::widget([ | |||
'filterModel' => $searchModel, | |||
'dataProvider' => $dataProvider, | |||
'columns' => [ | |||
'name', | |||
[ | |||
'class' => 'yii\grid\ActionColumn', | |||
'template' => '{update} {delete}', | |||
'headerOptions' => ['class' => 'column-actions'], | |||
'contentOptions' => ['class' => 'column-actions'], | |||
'buttons' => [ | |||
'update' => function ($url, $model) { | |||
return Html::a('<span class="glyphicon glyphicon-pencil"></span>', $url, [ | |||
'title' => Yii::t('app', 'Modifier'), 'class' => 'btn btn-default' | |||
]); | |||
}, | |||
'delete' => function ($url, $model) { | |||
return Html::a('<span class="glyphicon glyphicon-trash"></span>', $url, [ | |||
'title' => Yii::t('app', 'Supprimer'), 'class' => 'btn btn-default' | |||
]); | |||
} | |||
<?= GridView::widget([ | |||
'filterModel' => $searchModel, | |||
'dataProvider' => $dataProvider, | |||
'columns' => [ | |||
[ | |||
'attribute' => 'position', | |||
'headerOptions' => ['class' => 'position'], | |||
'format' => 'raw', | |||
'filter' => '', | |||
'value' => function ($model) { | |||
return '<a class="btn-position btn btn-default" href="javascript:void(0);"><span class="glyphicon glyphicon-resize-vertical"></span></a>'; | |||
} | |||
], | |||
'name', | |||
[ | |||
'class' => 'yii\grid\ActionColumn', | |||
'template' => '{update} {delete}', | |||
'headerOptions' => ['class' => 'column-actions'], | |||
'contentOptions' => ['class' => 'column-actions'], | |||
'buttons' => [ | |||
'update' => function ($url, $model) { | |||
return Html::a('<span class="glyphicon glyphicon-pencil"></span>', $url, [ | |||
'title' => Yii::t('app', 'Modifier'), 'class' => 'btn btn-default' | |||
]); | |||
}, | |||
'delete' => function ($url, $model) { | |||
return Html::a('<span class="glyphicon glyphicon-trash"></span>', $url, [ | |||
'title' => Yii::t('app', 'Supprimer'), 'class' => 'btn btn-default' | |||
]); | |||
} | |||
], | |||
], | |||
], | |||
], | |||
], | |||
]); ?> | |||
]); ?> | |||
</div> |
@@ -41,6 +41,7 @@ $(document).ready(function() { | |||
opendistrib_points_vente_acces() ; | |||
opendistrib_tooltip() ; | |||
opendistrib_ordre_produits() ; | |||
opendistrib_ordre_categories() ; | |||
opendistrib_products() ; | |||
opendistrib_product_prices() ; | |||
opendistrib_confirm_delete() ; | |||
@@ -229,9 +230,62 @@ function opendistrib_commandeauto() { | |||
$('#subscriptionform-date_begin, #subscriptionform-date_end').datepicker() ; | |||
} | |||
function opendistrib_sortable_list(element_selector, button_selector, route_ajax) { | |||
var fixHelper = function(e, ui) { | |||
ui.children().each(function() { | |||
$(this).width($(this).width()); | |||
}); | |||
return ui; | |||
}; | |||
$(element_selector+" table tbody").sortable({ | |||
items: "> tr", | |||
appendTo: "parent", | |||
cursor: "move", | |||
placeholder: "ui-state-highlight", | |||
handle: button_selector, | |||
helper: fixHelper, | |||
stop: function(event, ui) { | |||
var tab_ordre = {} ; | |||
var ordre = 1 ; | |||
if($('ul.pagination').size()) { | |||
var page = parseInt($('ul.pagination li.active a').html()) ; | |||
var nb_items_by_page = parseInt($('#page-size').html()) ; | |||
if(page != 1) { | |||
ordre = (page - 1) * nb_items_by_page ; | |||
} | |||
} | |||
$(element_selector+" table tbody tr").each(function() { | |||
tab_ordre[$(this).attr('data-key')] = ordre ; | |||
ordre++ ; | |||
}) ; | |||
$.post(UrlManager.getBaseUrl() + route_ajax,{ | |||
array: JSON.stringify(tab_ordre) | |||
}) ; | |||
} | |||
}).disableSelection(); | |||
} | |||
function opendistrib_ordre_categories() { | |||
opendistrib_sortable_list( | |||
'.product-category-index', | |||
'.btn-position', | |||
'product-category/position' | |||
) ; | |||
} | |||
function opendistrib_ordre_produits() { | |||
var fixHelper = function(e, ui) { | |||
opendistrib_sortable_list( | |||
'.product-index', | |||
'.btn-order', | |||
'product/order' | |||
) ; | |||
/*var fixHelper = function(e, ui) { | |||
ui.children().each(function() { | |||
$(this).width($(this).width()); | |||
}); | |||
@@ -267,7 +321,7 @@ function opendistrib_ordre_produits() { | |||
array: JSON.stringify(tab_ordre) | |||
}) ; | |||
} | |||
}).disableSelection(); | |||
}).disableSelection();*/ | |||
} | |||
function opendistrib_datepicker() { |
@@ -60,6 +60,7 @@ class ProductCategorySearch extends ProductCategory | |||
->with($optionsSearch['with']) | |||
->innerJoinWith($optionsSearch['join_with'], true) | |||
->where(['product_category.id_producer' => GlobalParam::getCurrentProducerId()]) | |||
->orderBy('product_category.position ASC') | |||
; | |||
$dataProvider = new ActiveDataProvider([ |
@@ -41,6 +41,7 @@ namespace producer\controllers; | |||
use common\helpers\Debug; | |||
use common\helpers\GlobalParam; | |||
use common\helpers\Mailjet; | |||
use common\models\ProductCategory; | |||
use common\models\ProductDistribution; | |||
use common\models\User; | |||
use common\models\Producer; | |||
@@ -669,6 +670,11 @@ class OrderController extends ProducerBaseController | |||
'distribution.date' => $date, | |||
]); | |||
// Catégories | |||
$categoriesArray = ProductCategory::searchAll([], ['orderby' => 'product_category.position ASC', 'as_array' => true]) ; | |||
array_unshift($categoriesArray, ['id' => null, 'name' => 'Catégorie par défaut']) ; | |||
$json['categories'] = $categoriesArray ; | |||
// Produits | |||
$productsArray = Product::find() | |||
->where([ |
@@ -227,45 +227,50 @@ $producer = GlobalParam::getCurrentProducer() ; | |||
</tr> | |||
</thead> | |||
<tbody> | |||
<tr v-for="product in products" v-if="product.productDistribution && product.productDistribution[0] && product.productDistribution[0].active == 1"> | |||
<td class="photo"> | |||
<img v-if="product.photo.length" class="photo-product" :src="'<?php echo Yii::$app->urlManager->getBaseUrl(); ?>/uploads/'+product.photo" /> | |||
</td> | |||
<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_max > 0 && ((product.quantity_form / product.coefficient_unit == product.quantity_remaining) || ((product.quantity_remaining * product.coefficient_unit) - product.quantity_form) < product.step)" class="label label-danger"> | |||
Épuisé | |||
</span> | |||
<div class="recipe" v-if="product.recipe.length">{{ product.recipe }}</div> | |||
</td> | |||
<td class="price-unit"> | |||
<template v-if="product.price_with_tax > 0">{{ formatPrice(product.price_with_tax) }}<br /><span class="unit">{{ product.wording_unit }}</span></template> | |||
</td> | |||
<td class="td-quantity"> | |||
<template v-if="product.price_with_tax > 0"> | |||
<div class="input-group"> | |||
<span class="input-group-btn"> | |||
<button class="btn btn-default btn-moins" type="button" @click="productQuantityClick(product, product.unit == 'piece' ? -1 : -parseFloat(product.step))" :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-addon">{{ product.unit == 'piece' ? 'p.' : product.unit }}</span> | |||
<span class="input-group-btn"> | |||
<button class="btn btn-default btn-plus" type="button" @click="productQuantityClick(product, product.unit == 'piece' ? 1 : parseFloat(product.step))" :disabled="product.quantity_form == product.quantity_remaining && product.quantity_max > 0"><span class="glyphicon glyphicon-plus"></span></button> | |||
</span> | |||
</div> | |||
</template> | |||
</td> | |||
<td class="price-total"> | |||
<template v-if="product.price_with_tax > 0 && product.quantity_form > 0"> | |||
{{ formatPrice(product.price_with_tax * (product.quantity_form / product.coefficient_unit )) }} | |||
</template> | |||
</td> | |||
</tr> | |||
<template v-for="category in categories"> | |||
<tr v-if="category.id && countProductsByCategory(category)"> | |||
<td class="category-name" colspan="5">{{ category.name }}</td> | |||
</tr> | |||
<tr v-for="product in products" v-if="product.id_product_category == category.id && product.productDistribution && product.productDistribution[0] && product.productDistribution[0].active == 1"> | |||
<td class="photo"> | |||
<img v-if="product.photo.length" class="photo-product" :src="'<?php echo Yii::$app->urlManager->getBaseUrl(); ?>/uploads/'+product.photo" /> | |||
</td> | |||
<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_max > 0 && ((product.quantity_form / product.coefficient_unit == product.quantity_remaining) || ((product.quantity_remaining * product.coefficient_unit) - product.quantity_form) < product.step)" class="label label-danger"> | |||
Épuisé | |||
</span> | |||
<div class="recipe" v-if="product.recipe.length">{{ product.recipe }}</div> | |||
</td> | |||
<td class="price-unit"> | |||
<template v-if="product.price_with_tax > 0">{{ formatPrice(product.price_with_tax) }}<br /><span class="unit">{{ product.wording_unit }}</span></template> | |||
</td> | |||
<td class="td-quantity"> | |||
<template v-if="product.price_with_tax > 0"> | |||
<div class="input-group"> | |||
<span class="input-group-btn"> | |||
<button class="btn btn-default btn-moins" type="button" @click="productQuantityClick(product, product.unit == 'piece' ? -1 : -parseFloat(product.step))" :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-addon">{{ product.unit == 'piece' ? 'p.' : product.unit }}</span> | |||
<span class="input-group-btn"> | |||
<button class="btn btn-default btn-plus" type="button" @click="productQuantityClick(product, product.unit == 'piece' ? 1 : parseFloat(product.step))" :disabled="product.quantity_form == product.quantity_remaining && product.quantity_max > 0"><span class="glyphicon glyphicon-plus"></span></button> | |||
</span> | |||
</div> | |||
</template> | |||
</td> | |||
<td class="price-total"> | |||
<template v-if="product.price_with_tax > 0 && product.quantity_form > 0"> | |||
{{ formatPrice(product.price_with_tax * (product.quantity_form / product.coefficient_unit )) }} | |||
</template> | |||
</td> | |||
</tr> | |||
</template> | |||
<tr class="total"> | |||
<td colspan="4"></td> | |||
<td class="price-total">{{ priceTotal(true) }}</td> |
@@ -1482,45 +1482,53 @@ termes. | |||
background-color: white; | |||
} | |||
/* line 223, ../sass/order/_order.scss */ | |||
.order-order #main #app-order-order table#products td.category-name { | |||
font-family: "capsuularegular"; | |||
font-size: 21px; | |||
line-height: 30px; | |||
text-transform: uppercase; | |||
padding-top: 13px; | |||
} | |||
/* line 232, ../sass/order/_order.scss */ | |||
.order-order #main #app-order-order table#products td.photo img { | |||
width: 100px; | |||
} | |||
/* line 229, ../sass/order/_order.scss */ | |||
/* line 238, ../sass/order/_order.scss */ | |||
.order-order #main #app-order-order table#products td.name .name { | |||
font-family: "capsuularegular"; | |||
color: black; | |||
font-size: 20px; | |||
line-height: 25px; | |||
} | |||
/* line 235, ../sass/order/_order.scss */ | |||
/* line 244, ../sass/order/_order.scss */ | |||
.order-order #main #app-order-order table#products td.name .other { | |||
font-size: 14px; | |||
color: #333; | |||
} | |||
/* line 239, ../sass/order/_order.scss */ | |||
/* line 248, ../sass/order/_order.scss */ | |||
.order-order #main #app-order-order table#products td.name .recipe { | |||
color: gray; | |||
} | |||
/* line 243, ../sass/order/_order.scss */ | |||
/* line 252, ../sass/order/_order.scss */ | |||
.order-order #main #app-order-order table#products .price-unit, .order-order #main #app-order-order table#products .price-total { | |||
width: 100px; | |||
text-align: center; | |||
} | |||
/* line 247, ../sass/order/_order.scss */ | |||
/* line 256, ../sass/order/_order.scss */ | |||
.order-order #main #app-order-order table#products .price-unit .unit, .order-order #main #app-order-order table#products .price-total .unit { | |||
color: gray; | |||
font-size: 13px; | |||
} | |||
/* line 252, ../sass/order/_order.scss */ | |||
/* line 261, ../sass/order/_order.scss */ | |||
.order-order #main #app-order-order table#products .td-quantity { | |||
width: 175px; | |||
} | |||
/* line 254, ../sass/order/_order.scss */ | |||
/* line 263, ../sass/order/_order.scss */ | |||
.order-order #main #app-order-order table#products .td-quantity input.quantity { | |||
text-align: center; | |||
border-right: 0px none; | |||
} | |||
/* line 258, ../sass/order/_order.scss */ | |||
/* line 267, ../sass/order/_order.scss */ | |||
.order-order #main #app-order-order table#products .td-quantity .input-group-addon { | |||
padding: 5px; | |||
padding-left: 0px; | |||
@@ -1528,30 +1536,30 @@ termes. | |||
border-left: 0px none; | |||
border-right: 0px none; | |||
} | |||
/* line 268, ../sass/order/_order.scss */ | |||
/* line 277, ../sass/order/_order.scss */ | |||
.order-order #main #app-order-order table#products tr.total .price-total { | |||
font-size: 23px; | |||
} | |||
/* line 276, ../sass/order/_order.scss */ | |||
/* line 285, ../sass/order/_order.scss */ | |||
.order-order #main #app-order-order #content-step-payment .credit .info { | |||
margin-left: 20px; | |||
color: gray; | |||
} | |||
/* line 282, ../sass/order/_order.scss */ | |||
/* line 291, ../sass/order/_order.scss */ | |||
.order-order #main #app-order-order #content-step-payment .comment { | |||
margin-bottom: 20px; | |||
} | |||
/* line 293, ../sass/order/_order.scss */ | |||
/* line 302, ../sass/order/_order.scss */ | |||
.order-order #main #app-order-order #infos { | |||
margin-top: 30px; | |||
} | |||
/* line 295, ../sass/order/_order.scss */ | |||
/* line 304, ../sass/order/_order.scss */ | |||
.order-order #main #app-order-order #infos .panel-body { | |||
padding-top: 0px; | |||
white-space: pre-line; | |||
} | |||
/* line 305, ../sass/order/_order.scss */ | |||
/* line 314, ../sass/order/_order.scss */ | |||
#main #content .panel h3 { | |||
font-family: "highvoltageregular"; | |||
margin: 0px; |
@@ -15,6 +15,7 @@ var app = new Vue({ | |||
pointSaleActive: null, | |||
pointsSaleCodes: [], | |||
products: [], | |||
categories: [], | |||
comment: '', | |||
creditCheckbox: false, | |||
useCredit: false, | |||
@@ -189,6 +190,10 @@ var app = new Vue({ | |||
app.products = response.data.products ; | |||
} | |||
if(response.data.categories) { | |||
app.categories = response.data.categories ; | |||
} | |||
if(!updateOnlyProducts) { | |||
app.order = null ; | |||
if(response.data.order) { | |||
@@ -362,6 +367,15 @@ var app = new Vue({ | |||
total = this.priceTotal() - order.amount_paid ; | |||
} | |||
return this.producer.credit_limit == null || (this.producer.credit_limit != null && (this.user.credit - total >= this.producer.credit_limit)) ; | |||
}, | |||
countProductsByCategory: function(category) { | |||
var count = 0 ; | |||
for(var i = 0 ; i < this.products.length ; i++) { | |||
if(this.products[i].id_product_category == category.id) { | |||
count ++ ; | |||
} | |||
} | |||
return count ; | |||
} | |||
}, | |||
computed : { |
@@ -219,6 +219,15 @@ | |||
} | |||
table#products { | |||
td.category-name { | |||
font-family: "capsuularegular" ; | |||
font-size: 21px ; | |||
line-height: 30px; | |||
text-transform: uppercase; | |||
padding-top: 13px ; | |||
} | |||
td.photo { | |||
img { | |||
width: 100px ; |