@@ -423,10 +423,12 @@ class DistributionController extends BackendController | |||
'quantity' => 0, | |||
'unit' => $product->unit, | |||
'price' => $product->getPriceWithTax([ | |||
'user' => $user, | |||
'user_producer' => $userProducer, | |||
'point_sale' => $pointSale | |||
]), | |||
'user' => $user, | |||
'user_producer' => $userProducer, | |||
'point_sale' => $pointSale | |||
]), | |||
'active' => $product->productDistribution[0]->active | |||
&& (!$pointSale || $product->isAvailableOnPointSale($pointSale)) | |||
]; | |||
} | |||
@@ -133,6 +133,9 @@ class ProductController extends BackendController | |||
Upload::uploadFile($model, 'photo'); | |||
$model->save(); | |||
// availability on points sale | |||
$this->processAvailabilityPointsSale($model); | |||
// link product / distribution | |||
Distribution::linkProductIncomingDistributions($model) ; | |||
@@ -159,6 +162,11 @@ class ProductController extends BackendController | |||
$request = Yii::$app->request; | |||
$model = $this->findModel($id); | |||
foreach ($model->productPointSale as $productPointSale) { | |||
$model->pointsSale[] = $productPointSale->id_point_sale; | |||
} | |||
$photoFilenameOld = $model->photo; | |||
if ($model->load(Yii::$app->request->post()) && $model->save()) { | |||
@@ -171,6 +179,9 @@ class ProductController extends BackendController | |||
$model->save(); | |||
} | |||
// availability on points sale | |||
$this->processAvailabilityPointsSale($model); | |||
if ($model->apply_distributions) { | |||
// link product / distribution | |||
Distribution::linkProductIncomingDistributions($model); | |||
@@ -186,6 +197,27 @@ class ProductController extends BackendController | |||
]); | |||
} | |||
/** | |||
* Traite les accès restreints d'un point de vente. | |||
*/ | |||
public function processAvailabilityPointsSale($model) | |||
{ | |||
ProductPointSale::deleteAll(['id_product' => $model->id]); | |||
if (is_array($model->pointsSale) && count($model->pointsSale)) { | |||
foreach ($model->pointsSale as $key => $val) { | |||
$pointSale = PointSale::findOne($val); | |||
if ($pointSale) { | |||
$productPointSale = new ProductPointSale; | |||
$productPointSale->id_product = $model->id; | |||
$productPointSale->id_point_sale = $pointSale->id; | |||
$productPointSale->available = ($model->available_on_points_sale) ? 0 : 1; | |||
$productPointSale->save(); | |||
} | |||
} | |||
} | |||
} | |||
public function actionPricesList($id) | |||
{ | |||
$request = Yii::$app->request; |
@@ -229,6 +229,7 @@ $this->setPageTitle('Distributions') ; | |||
:users="users" | |||
:products="products" | |||
:producer="producer" | |||
:loading-update-product-order="loadingUpdateProductOrder" | |||
@close="showModalFormOrderCreate = false" | |||
@ordercreatedupdated="orderCreatedUpdated" | |||
@updateproductorderprices="updateProductOrderPrices" | |||
@@ -401,6 +402,7 @@ $this->setPageTitle('Distributions') ; | |||
:products="products" | |||
:order="ordersUpdate[key]" | |||
:producer="producer" | |||
:loading-update-product-order="loadingUpdateProductOrder" | |||
@close="showModalFormOrderUpdate = false" | |||
@ordercreatedupdated="orderCreatedUpdated" | |||
@updateproductorderprices="updateProductOrderPrices" | |||
@@ -606,7 +608,7 @@ $this->setPageTitle('Distributions') ; | |||
<tbody> | |||
<tr v-for="product in products" :class="(order.productOrder[product.id] > 0) ? 'product-ordered' : ''"> | |||
<td> | |||
<span class="label label-success" v-if="product.productDistribution[0].active == 1">Actif</span> | |||
<span class="label label-success" v-if="loadingUpdateProductOrder || order.productOrder[product.id].active">Actif</span> | |||
<span class="label label-danger" v-else>Inactif</span> | |||
</td> | |||
<td>{{ product.name }}</td> |
@@ -118,7 +118,7 @@ use common\helpers\GlobalParam; | |||
$url = Yii::$app->urlManagerProducer->getHostInfo() . '/' . Yii::$app->urlManagerProducer->baseUrl . '/uploads/' . $model->photo; | |||
$url = str_replace('//uploads','/uploads', $url) ; | |||
echo '<img class="photo-product" src="' . $url . '" width="200px" /><br />'; | |||
echo '<input type="checkbox" name="delete_photo" id="delete_photo" /> <label for="delete_photo">Supprimer la photo</label><br /><br />'; | |||
echo '<input type="checkbox" name="delete_photo" id="delete_photo" /> <label for="delete_photo">Supprimer la photo</label>'; | |||
} | |||
?> | |||
@@ -133,6 +133,20 @@ use common\helpers\GlobalParam; | |||
<?= $form->field($model, 'sunday')->checkbox() ?> | |||
</div> | |||
<div class="clr"></div> | |||
<div id="availability-points-sale"> | |||
<h2>Disponibilité points de vente</h2> | |||
<?= $form->field($model, 'available_on_points_sale')->radioList([1 => 'Disponible', 0 => 'Indisponible']) ?> | |||
<strong id="label-availability-points-sale">Et <span><?php if($model->available_on_points_sale): ?>indisponible<?php else: ?>disponible<?php endif; ?></span> sur les points de vente</strong> | |||
<?php $pointSaleArray = PointSale::find() | |||
->where(['id_producer' => GlobalParam::getCurrentProducerId()]) | |||
->orderBy('is_bread_box ASC, name ASC') | |||
->all(); ?> | |||
<?= Html::activeCheckboxList($model, 'pointsSale', ArrayHelper::map($pointSaleArray, 'id', function ($pointSale, $defaultValue) use ($model) { | |||
return Html::encode($pointSale->name) ; | |||
}), ['encode' => false, 'class' => '']) ?> | |||
</div> | |||
</div> | |||
<div class="clr"></div> | |||
</div> |
@@ -1827,19 +1827,47 @@ body.login-page .login-box .login-box-body a:hover { | |||
} | |||
/* line 10, ../sass/product/_form.scss */ | |||
.product-create #days-production, | |||
.product-update #days-production { | |||
.product-create #availability-points-sale, | |||
.product-update #days-production, | |||
.product-update #availability-points-sale { | |||
margin-top: 30px; | |||
} | |||
/* line 13, ../sass/product/_form.scss */ | |||
/* line 14, ../sass/product/_form.scss */ | |||
.product-create #days-production h2, | |||
.product-update #days-production h2 { | |||
.product-create #availability-points-sale h2, | |||
.product-update #days-production h2, | |||
.product-update #availability-points-sale h2 { | |||
font-size: 20px; | |||
} | |||
/* line 16, ../sass/product/_form.scss */ | |||
/* line 20, ../sass/product/_form.scss */ | |||
.product-create #days-production label, | |||
.product-update #days-production label { | |||
font-weight: normal; | |||
} | |||
/* line 26, ../sass/product/_form.scss */ | |||
.product-create #availability-points-sale #label-availability-points-sale, | |||
.product-update #availability-points-sale #label-availability-points-sale { | |||
display: block; | |||
margin-bottom: 6px; | |||
} | |||
/* line 30, ../sass/product/_form.scss */ | |||
.product-create #availability-points-sale #label-availability-points-sale span, | |||
.product-update #availability-points-sale #label-availability-points-sale span { | |||
border-bottom: dotted 1px black; | |||
} | |||
/* line 36, ../sass/product/_form.scss */ | |||
.product-create #availability-points-sale .field-product-available_on_points_sale label.control-label, | |||
.product-update #availability-points-sale .field-product-available_on_points_sale label.control-label { | |||
margin-bottom: 0px; | |||
position: relative; | |||
top: 3px; | |||
} | |||
/* line 44, ../sass/product/_form.scss */ | |||
.product-create #availability-points-sale #product-pointssale label, | |||
.product-update #availability-points-sale #product-pointssale label { | |||
display: block; | |||
font-weight: normal; | |||
} | |||
/** | |||
Copyright distrib (2018) |
@@ -45,6 +45,7 @@ $(document).ready(function() { | |||
opendistrib_products() ; | |||
opendistrib_product_prices() ; | |||
opendistrib_confirm_delete() ; | |||
opendistrib_product_availability_points_sale(); | |||
}) ; | |||
var UrlManager = { | |||
@@ -57,6 +58,18 @@ var UrlManager = { | |||
} | |||
}; | |||
function opendistrib_product_availability_points_sale() { | |||
$('input[name="Product[available_on_points_sale]"]').change(function() { | |||
var available = parseInt($(this).val()); | |||
var label = 'disponible'; | |||
if(available == 1) { | |||
label = 'indisponible'; | |||
} | |||
$('#label-availability-points-sale span').html(label); | |||
}); | |||
} | |||
function opendistrib_confirm_delete() { | |||
$('.btn-confirm-delete').click(function(event) { | |||
if(!confirm('Souhaitez-vous vraiment supprimer cette entrée ?')) { |
@@ -76,6 +76,7 @@ var app = new Vue({ | |||
checkboxSelectAllOrders: false, | |||
messageGenerateDeliveryNoteDisplayed: false, | |||
missingSubscriptions: false, | |||
loadingUpdateProductOrder: false, | |||
calendar: { | |||
mode: 'single', | |||
attrs: [], | |||
@@ -364,6 +365,7 @@ var app = new Vue({ | |||
this.idOrderUpdate = idOrder ; | |||
this.showModalFormOrderUpdate = true ; | |||
this.initModalFormOrder() ; | |||
this.updateProductOrderPrices(); | |||
}, | |||
openModalFormOrderCreate: function() { | |||
this.showModalFormOrderCreate = true ; | |||
@@ -569,6 +571,7 @@ var app = new Vue({ | |||
updateProductOrderPrices: function() { | |||
var app = this ; | |||
app.loadingUpdateProductOrder = true; | |||
var order = null ; | |||
if(app.showModalFormOrderCreate) { | |||
@@ -595,17 +598,21 @@ var app = new Vue({ | |||
for (idProduct in response.data) { | |||
if (app.showModalFormOrderCreate) { | |||
Vue.set(app.orderCreate.productOrder[idProduct], 'price', response.data[idProduct].price); | |||
Vue.set(app.orderCreate.productOrder[idProduct], 'active', response.data[idProduct].active); | |||
} | |||
if (app.showModalFormOrderUpdate && app.idOrderUpdate) { | |||
for (keyOrderUpdate in app.ordersUpdate) { | |||
if (order.id == app.idOrderUpdate) { | |||
Vue.set(app.ordersUpdate[keyOrderUpdate].productOrder[idProduct], 'price', response.data[idProduct].price); | |||
Vue.set(app.ordersUpdate[keyOrderUpdate].productOrder[idProduct], 'active', response.data[idProduct].active); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
app.loadingUpdateProductOrder = false; | |||
}); | |||
} | |||
} | |||
@@ -617,7 +624,7 @@ Vue.component('modal', { | |||
}) | |||
Vue.component('order-form',{ | |||
props: ['date', 'pointsSale','meansPayment', 'users', 'products', 'order', 'producer'], | |||
props: ['date', 'pointsSale','meansPayment', 'users', 'products', 'order', 'producer', 'loadingUpdateProductOrder'], | |||
data: function() { | |||
return { | |||
errors: [], |
@@ -7,16 +7,46 @@ | |||
margin-right: 15px ; | |||
} | |||
} | |||
#days-production { | |||
#days-production, | |||
#availability-points-sale { | |||
margin-top: 30px ; | |||
h2 { | |||
font-size: 20px ; | |||
} | |||
} | |||
#days-production { | |||
label { | |||
font-weight: normal ; | |||
} | |||
} | |||
#availability-points-sale { | |||
#label-availability-points-sale { | |||
display: block; | |||
margin-bottom: 6px; | |||
span { | |||
border-bottom: dotted 1px black; | |||
} | |||
} | |||
.field-product-available_on_points_sale { | |||
label.control-label { | |||
margin-bottom: 0px; | |||
position: relative; | |||
top: 3px; | |||
} | |||
} | |||
#product-pointssale { | |||
label { | |||
display: block; | |||
font-weight: normal; | |||
} | |||
} | |||
} | |||
} | |||
@@ -66,6 +66,8 @@ class Product extends ActiveRecordCommon | |||
public $price_with_tax = 0 ; | |||
public $wording_unit = '' ; | |||
public $pointsSale; | |||
public static $unitsArray = [ | |||
'piece' => [ | |||
'unit' => 'piece', | |||
@@ -120,7 +122,7 @@ class Product extends ActiveRecordCommon | |||
return [ | |||
[['name', 'id_producer'], 'required'], | |||
[['active', 'order', 'id_producer', 'id_tax_rate', 'id_product_category'], 'integer'], | |||
[['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday', 'unavailable', 'apply_distributions'], 'boolean'], | |||
[['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday', 'unavailable', 'apply_distributions', 'available_on_points_sale'], 'boolean'], | |||
[['price', 'weight', 'step', 'quantity_max', 'quantity_max_monday', 'quantity_max_tuesday', 'quantity_max_wednesday', 'quantity_max_thursday', 'quantity_max_friday', 'quantity_max_saturday', 'quantity_max_sunday'], 'number'], | |||
[['photo'], 'file'], | |||
[['name', 'reference', 'description', 'photo', 'unit'], 'string', 'max' => 255], | |||
@@ -131,7 +133,7 @@ class Product extends ActiveRecordCommon | |||
} | |||
return false; | |||
}], | |||
[['price_with_tax', 'wording_unit'], 'safe'] | |||
[['price_with_tax', 'wording_unit', 'pointsSale'], 'safe'] | |||
]; | |||
} | |||
@@ -148,7 +150,6 @@ class Product extends ActiveRecordCommon | |||
'active' => 'Actif', | |||
'photo' => 'Photo', | |||
'price' => 'Prix (€) TTC', | |||
'price' => 'Prix (€) TTC', | |||
'weight' => 'Poids', | |||
'recipe' => 'Recette', | |||
'monday' => 'Lundi', | |||
@@ -172,7 +173,8 @@ class Product extends ActiveRecordCommon | |||
'unit' => 'Unité', | |||
'step' => 'Pas', | |||
'id_tax_rate' => 'TVA', | |||
'id_product_category' => 'Catégorie' | |||
'id_product_category' => 'Catégorie', | |||
'available_on_points_sale' => 'Par défaut' | |||
]; | |||
} | |||
@@ -215,6 +217,11 @@ class Product extends ActiveRecordCommon | |||
return $this->hasOne(ProductCategory::className(), ['id' => 'id_product_category']) ; | |||
} | |||
public function getProductPointSale() | |||
{ | |||
return $this->hasMany(ProductPointSale::className(), ['id_product' => 'id']) ; | |||
} | |||
/** | |||
* Retourne les options de base nécessaires à la fonction de recherche. | |||
* | |||
@@ -223,13 +230,41 @@ class Product extends ActiveRecordCommon | |||
public static function defaultOptionsSearch() | |||
{ | |||
return [ | |||
'with' => ['taxRate'], | |||
'with' => ['taxRate', 'productPointSale'], | |||
'join_with' => [], | |||
'orderby' => 'order ASC', | |||
'attribute_id_producer' => 'product.id_producer' | |||
]; | |||
} | |||
public function isAvailableOnPointSale($pointSale) | |||
{ | |||
// disponible par défaut | |||
if($this->available_on_points_sale) { | |||
foreach($this->productPointSale as $productPointSale) { | |||
if($pointSale->id == $productPointSale->id_point_sale | |||
//&& $productPointSale->id_product == $this->id | |||
&& !$productPointSale->available) { | |||
return false; | |||
} | |||
} | |||
return true; | |||
} | |||
// indisponible par défaut | |||
else { | |||
foreach($this->productPointSale as $productPointSale) { | |||
if($pointSale->id == $productPointSale->id_point_sale | |||
//&& $productPointSale->id_product == $this->id | |||
&& $productPointSale->available) { | |||
return true; | |||
} | |||
} | |||
return false; | |||
} | |||
} | |||
/** | |||
* Retourne la description du produit. | |||
* |
@@ -0,0 +1,111 @@ | |||
<?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. | |||
*/ | |||
namespace common\models; | |||
use common\components\ActiveRecordCommon ; | |||
/** | |||
* This is the model class for table "product_point_sale". | |||
* | |||
* @property integer $id_product | |||
* @property integer $id_point_sale | |||
* @property boolean $available | |||
*/ | |||
class ProductPointSale extends ActiveRecordCommon | |||
{ | |||
/** | |||
* @inheritdoc | |||
*/ | |||
public static function tableName() | |||
{ | |||
return 'product_point_sale'; | |||
} | |||
/** | |||
* @inheritdoc | |||
*/ | |||
public function rules() | |||
{ | |||
return [ | |||
[['id_product', 'id_point_sale'], 'required'], | |||
[['id_product', 'id_point_sale'], 'integer'], | |||
[['available'], 'boolean'], | |||
]; | |||
} | |||
/** | |||
* @inheritdoc | |||
*/ | |||
public function attributeLabels() | |||
{ | |||
return [ | |||
'id_product' => 'Produit', | |||
'id_point_sale' => 'Point de vente', | |||
'available' => 'Disponible', | |||
]; | |||
} | |||
/* | |||
* Relations | |||
*/ | |||
public function getProduct() | |||
{ | |||
return $this->hasOne(Product::className(), ['id' => 'id_product']); | |||
} | |||
public function getPointSale() | |||
{ | |||
return $this->hasOne(PointSale::className(), ['id' => 'id_point_sale']); | |||
} | |||
/** | |||
* Retourne les options de base nécessaires à la fonction de recherche. | |||
* | |||
* @return array | |||
*/ | |||
public static function defaultOptionsSearch() { | |||
return [ | |||
'with' => ['product', 'pointSale'], | |||
'join_with' => [], | |||
'orderby' => '', | |||
'attribute_id_producer' => '' | |||
] ; | |||
} | |||
} |
@@ -0,0 +1,33 @@ | |||
<?php | |||
use yii\db\Migration; | |||
use yii\db\Schema; | |||
/** | |||
* Class m220209_095208_product_availability_point_sale | |||
*/ | |||
class m220209_095208_product_availability_point_sale extends Migration | |||
{ | |||
/** | |||
* {@inheritdoc} | |||
*/ | |||
public function safeUp() | |||
{ | |||
$this->addColumn('product', 'available_on_points_sale', Schema::TYPE_BOOLEAN.' DEFAULT 1'); | |||
$this->createTable('product_point_sale', [ | |||
'id' => 'pk', | |||
'id_product' => Schema::TYPE_INTEGER. ' NOT NULL', | |||
'id_point_sale' => Schema::TYPE_INTEGER. ' NOT NULL', | |||
'available' => Schema::TYPE_BOOLEAN.' DEFAULT 1', | |||
]); | |||
} | |||
/** | |||
* {@inheritdoc} | |||
*/ | |||
public function safeDown() | |||
{ | |||
$this->dropColumn('product', 'available_on_points_sale'); | |||
$this->dropTable('product_point_sale'); | |||
} | |||
} |
@@ -810,20 +810,32 @@ class OrderController extends ProducerBaseController | |||
]); | |||
$productsArray = $productsArray->joinWith([ | |||
'productDistribution' => function ($query) use ( | |||
$distribution | |||
) { | |||
'productDistribution' => function ($query) use ($distribution) { | |||
$query->andOnCondition( | |||
'product_distribution.id_distribution = ' . $distribution->id | |||
); | |||
}, | |||
/*'productPointSale' => function ($query) use ($pointSaleCurrent) { | |||
$query->andOnCondition( | |||
'product_point_sale.id_point_sale = ' . $pointSaleCurrent->id | |||
); | |||
},*/ | |||
'productPrice' | |||
]) | |||
->orderBy('product_distribution.active DESC, order ASC') | |||
->all(); | |||
$productsArrayFilter = []; | |||
// filtre sur les points de vente | |||
foreach($productsArray as $product) { | |||
if($product->isAvailableOnPointSale($pointSaleCurrent)) { | |||
$productsArrayFilter[] = $product; | |||
} | |||
} | |||
$indexProduct = 0; | |||
foreach ($productsArray as &$product) { | |||
foreach ($productsArrayFilter as $key => &$product) { | |||
$product = array_merge( | |||
$product->getAttributes(), | |||
[ | |||
@@ -832,7 +844,8 @@ class OrderController extends ProducerBaseController | |||
'user_producer' => $userProducer, | |||
'point_sale' => $pointSaleCurrent | |||
]), | |||
'productDistribution' => $product['productDistribution'] | |||
'productDistribution' => $product['productDistribution'], | |||
'productPointSale' => $product['productPointSale'], | |||
] | |||
); | |||
@@ -870,7 +883,7 @@ class OrderController extends ProducerBaseController | |||
$product['index'] = $indexProduct++; | |||
} | |||
$json['products'] = $productsArray; | |||
$json['products'] = $productsArrayFilter; | |||
} else { | |||
$json['points_sale'] = $this->_initPointsSale($producer->id); | |||
} |