Browse Source

Merge branch 'develop'

master
Guillaume Bourgeois 1 year ago
parent
commit
4f62bbfc58
98 changed files with 3338 additions and 636 deletions
  1. +58
    -0
      backend/assets/VuejsSettingFormAsset.php
  2. +2
    -1
      backend/controllers/DistributionController.php
  3. +19
    -7
      backend/controllers/FeatureAdminController.php
  4. +3
    -0
      backend/controllers/ProducerController.php
  5. +86
    -0
      backend/controllers/SettingAdminController.php
  6. +1
    -1
      backend/controllers/UserController.php
  7. +57
    -0
      backend/forms/AdminSettingsForm.php
  8. +13
    -28
      backend/views/distribution/shopping-cart-labels.php
  9. +10
    -1
      backend/views/feature-admin/index.php
  10. +8
    -1
      backend/views/feature-admin/update.php
  11. +4
    -3
      backend/views/layouts/left.php
  12. +2
    -0
      backend/views/point-sale/_form.php
  13. +41
    -17
      backend/views/producer/update.php
  14. +112
    -0
      backend/views/setting-admin/index.php
  15. +7
    -2
      backend/views/support/index.php
  16. +44
    -1
      backend/web/css/screen.css
  17. +7
    -1
      backend/web/js/backend.js
  18. +63
    -0
      backend/web/js/vuejs/setting-form.js
  19. +2
    -1
      backend/web/sass/screen.scss
  20. +52
    -0
      backend/web/sass/setting/_form.scss
  21. +2
    -0
      common/assets/CommonAsset.php
  22. +2
    -1
      common/components/BusinessLogic.php
  23. +8
    -2
      common/components/BusinessLogicTrait.php
  24. +2
    -1
      common/components/DolibarrApi.php
  25. +1
    -1
      common/config/params.php
  26. +8
    -0
      common/logic/AbstractChecker.php
  27. +8
    -1
      common/logic/AbstractRepository.php
  28. +8
    -0
      common/logic/AbstractResolver.php
  29. +1
    -0
      common/logic/AbstractService.php
  30. +8
    -0
      common/logic/CheckerInterface.php
  31. +120
    -29
      common/logic/Distribution/Distribution/Export/DistributionShoppingCartLabelsPdfGenerator.php
  32. +7
    -1
      common/logic/Document/Document/Service/DocumentManager.php
  33. +8
    -4
      common/logic/Feature/Feature/Feature.php
  34. +2
    -4
      common/logic/Feature/Feature/FeatureBuilder.php
  35. +48
    -0
      common/logic/Feature/Feature/FeatureChecker.php
  36. +24
    -0
      common/logic/Feature/Feature/FeatureDefinition.php
  37. +3
    -6
      common/logic/Feature/Feature/FeatureImporter.php
  38. +43
    -0
      common/logic/Feature/Feature/FeatureManager.php
  39. +7
    -6
      common/logic/Feature/Feature/FeatureModule.php
  40. +9
    -3
      common/logic/Feature/Feature/FeatureRepository.php
  41. +9
    -2
      common/logic/Feature/Feature/FeatureRepositoryQuery.php
  42. +0
    -31
      common/logic/Feature/Feature/Service/FeatureDefinition.php
  43. +0
    -80
      common/logic/Feature/Feature/Service/FeatureManager.php
  44. +2
    -2
      common/logic/Feature/FeatureProducer/FeatureProducer.php
  45. +2
    -5
      common/logic/Feature/FeatureProducer/FeatureProducerBuilder.php
  46. +13
    -0
      common/logic/Feature/FeatureProducer/FeatureProducerDefinition.php
  47. +2
    -5
      common/logic/Feature/FeatureProducer/FeatureProducerModule.php
  48. +2
    -3
      common/logic/Feature/FeatureProducer/FeatureProducerRepository.php
  49. +3
    -4
      common/logic/Feature/FeatureProducer/FeatureProducerRepositoryQuery.php
  50. +0
    -25
      common/logic/Feature/FeatureProducer/Service/FeatureProducerDefinition.php
  51. +4
    -2
      common/logic/Order/Order/Service/TillerManager.php
  52. +3
    -2
      common/logic/PointSale/PointSale/Model/PointSale.php
  53. +14
    -4
      common/logic/Producer/Producer/Model/Producer.php
  54. +9
    -4
      common/logic/ProducerContextTrait.php
  55. +39
    -0
      common/logic/Setting/AdminSettingBag.php
  56. +108
    -0
      common/logic/Setting/Setting.php
  57. +84
    -0
      common/logic/Setting/SettingBuilder.php
  58. +47
    -0
      common/logic/Setting/SettingDefinition.php
  59. +170
    -0
      common/logic/Setting/SettingDetails/AbstractSettingDetail.php
  60. +67
    -0
      common/logic/Setting/SettingDetails/Admin/AdminSettingDefinition.php
  61. +17
    -0
      common/logic/Setting/SettingDetails/Admin/General/AdministratorEmailAdminSetting.php
  62. +17
    -0
      common/logic/Setting/SettingDetails/Admin/General/AdministratorPhoneNumberAdminSetting.php
  63. +30
    -0
      common/logic/Setting/SettingDetails/Producer/ProducerSettingDefinition.php
  64. +37
    -0
      common/logic/Setting/SettingImporter.php
  65. +53
    -0
      common/logic/Setting/SettingModule.php
  66. +50
    -0
      common/logic/Setting/SettingRepository.php
  67. +27
    -0
      common/logic/Setting/SettingRepositoryQuery.php
  68. +2
    -0
      common/mail/orderConfirm-html.php
  69. +1
    -1
      common/mail/paymentErrorProducer-text.php
  70. +32
    -0
      common/versions/23.11.A.php
  71. +31
    -0
      common/versions/23.11.B.php
  72. +386
    -0
      common/web/js/simple-lightbox/simpleLightbox.css
  73. +532
    -0
      common/web/js/simple-lightbox/simpleLightbox.js
  74. +1
    -0
      common/web/js/simple-lightbox/simpleLightbox.min.css
  75. +1
    -0
      common/web/js/simple-lightbox/simpleLightbox.min.js
  76. +2
    -1
      composer.json
  77. +104
    -2
      composer.lock
  78. +2
    -1
      console/commands/ImportFeaturesController.php
  79. +18
    -0
      console/commands/SettingController.php
  80. +25
    -0
      console/migrations/m231113_073008_add_column_feature_position.php
  81. +26
    -0
      console/migrations/m231113_084553_add_column_point_sale_minimum_order_amount.php
  82. +26
    -0
      console/migrations/m231113_131131_add_column_producer_export_shopping_cart_labels_format.php
  83. +37
    -0
      console/migrations/m231114_085352_create_table_setting.php
  84. +26
    -0
      console/migrations/m231115_083420_add_column_producer_document_image_bottom.php
  85. +4
    -1
      frontend/controllers/SiteController.php
  86. +7
    -6
      frontend/views/site/_prices_producer.php
  87. +4
    -1
      frontend/views/site/service.php
  88. +131
    -125
      producer/controllers/CreditController.php
  89. +33
    -35
      producer/controllers/OrderController.php
  90. +2
    -2
      producer/controllers/SiteController.php
  91. +4
    -1
      producer/views/credit/history.php
  92. +3
    -3
      producer/views/layouts/main.php
  93. +123
    -124
      producer/views/order/order.php
  94. +1
    -1
      producer/views/site/index.php
  95. +36
    -34
      producer/web/css/screen.css
  96. +5
    -3
      producer/web/js/producer.js
  97. +11
    -1
      producer/web/js/vuejs/order-order.js
  98. +3
    -2
      producer/web/sass/order/_order.scss

+ 58
- 0
backend/assets/VuejsSettingFormAsset.php View File

@@ -0,0 +1,58 @@
<?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 backend\assets;

use yii\web\AssetBundle;
use yii ;

class VuejsSettingFormAsset 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/setting-form.js') ;
}
}

+ 2
- 1
backend/controllers/DistributionController.php View File

@@ -577,7 +577,8 @@ class DistributionController extends BackendController
return $distributionModule->getExportManager()->getGenerator($name)->generate($distribution);
}
catch(ErrorException $exception) {
$this->setFlash('error', "Une erreur est survenue lors de la génération de l'export.");
//$this->setFlash('error', "Une erreur est survenue lors de la génération de l'export.");
$this->setFlash('error', $exception->getMessage());
return $this->redirectReferer();
}
}

+ 19
- 7
backend/controllers/FeatureAdminController.php View File

@@ -71,7 +71,7 @@ class FeatureAdminController extends BackendController
public function actionIndex()
{
$featureModule = $this->getFeatureModule();
$dataProviderFeatures = $featureModule->getRepository()->queryAll()->getDataProvider(100);
$dataProviderFeatures = $featureModule->getRepository()->queryDefaultAll()->getDataProvider(100);

return $this->render('index', [
'producerCurrent' => $this->getProducerCurrent(),
@@ -85,30 +85,30 @@ class FeatureAdminController extends BackendController
$feature = $this->findModel($id);

if($status) {
$featureManager->enable($feature);
$featureManager->enableFeature($feature);
$messageResponse = 'La fonctionnalité "'.Html::encode($feature->name).'" a bien été activée';
}
else {
$featureManager->disable($feature);
$featureManager->disableFeature($feature);
$messageResponse = 'La fonctionnalité "'.Html::encode($feature->name).'" a bien été désactivée';
}

return Ajax::responseSuccess($messageResponse);
}

public function actionToggleStatusFeatureProducer(int $id, bool $status = null)
public function actionToggleStatusFeatureProducer(int $id, int $status = null)
{
$featureManager = $this->getFeatureModule()->getManager();
$feature = $this->findModel($id);

if(is_null($status)) {
$featureManager->defaultForProducer($feature);
$featureManager->defaultFeatureForProducer($feature);
}
elseif($status == 0) {
$featureManager->disableForProducer($feature);
$featureManager->disableFeatureForProducer($feature);
}
elseif($status == 1) {
$featureManager->enableForProducer($feature);
$featureManager->enableFeatureForProducer($feature);
}

return $this->redirectReferer();
@@ -128,6 +128,18 @@ class FeatureAdminController extends BackendController
}
}

public function actionPosition()
{
$array = \Yii::$app->request->post('array');
$positionArray = json_decode(stripslashes($array));

foreach ($positionArray as $id => $position) {
$feature = $this->findModel($id);
$feature->position = $position;
$feature->save();
}
}

protected function findModel($id)
{
$featureModule = $this->getFeatureModule();

+ 3
- 0
backend/controllers/ProducerController.php View File

@@ -94,17 +94,20 @@ class ProducerController extends BackendController

$logoFilenameOld = $model->logo;
$photoFilenameOld = $model->photo;
$documentImageBottomFilenameOld = $model->document_image_bottom;
$producerBuilder->initOptionDashboardDatesDisplay($model);

if ($model->load(\Yii::$app->request->post())) {

$model->logoFile = UploadedFile::getInstance($model, 'logoFile');
$model->photoFile = UploadedFile::getInstance($model, 'photoFile');
$model->document_image_bottomFile = UploadedFile::getInstance($model, 'document_image_bottomFile');

if($model->validate()) {

$producerBuilder->processUploadImage($model, 'logo', $logoFilenameOld, $request->post('delete_logo', 0));
$producerBuilder->processUploadImage($model, 'photo', $photoFilenameOld, $request->post('delete_photo', 0));
$producerBuilder->processUploadImage($model, 'document_image_bottom', $documentImageBottomFilenameOld, $request->post('delete_document_image_bottom', 0));
$producerBuilder->initOptionDashboardDatesBeforeSave($model);
$producerBuilder->savePrivateKeysStripe($model);
$model->save();

+ 86
- 0
backend/controllers/SettingAdminController.php View File

@@ -0,0 +1,86 @@
<?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 backend\controllers;

use backend\forms\AdminSettingsForm;
use common\helpers\Ajax;
use yii\filters\AccessControl;
use yii\helpers\Html;
use yii\web\NotFoundHttpException;

/**
* UserController implements the CRUD actions for User model.
*/
class SettingAdminController extends BackendController
{
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::class,
'rules' => [
[
'allow' => true,
'roles' => ['@'],
'matchCallback' => function ($rule, $action) {
return $this->getUserModule()
->getAuthorizationChecker()
->isGrantedAsAdministrator($this->getUserCurrent());
}
]
],
],
];
}

public function actionIndex()
{
$settingModule = $this->getSettingModule();
$model = new AdminSettingsForm();
if($model->load(\Yii::$app->request->post()) && $model->validate()) {
foreach($settingModule->getAdminSettingDefinition()->getSettingDetailsFlat() as $settingDetail) {
$settingModule->getAdminSettingBag()->set($settingDetail->getName(), $model->{$settingDetail->getName()});
}
}

return $this->render('index', [
'model' => $model
]);
}
}

+ 1
- 1
backend/controllers/UserController.php View File

@@ -532,7 +532,7 @@ class UserController extends BackendController
}
}

$pointsSaleArray = PointSale::find()->where(['id_producer' => GlobalParam::getCurrentProducerId()])->all();
$pointsSaleArray = PointSale::find()->where(['id_producer' => GlobalParam::getCurrentProducerId(), 'status' => 1])->all();

$pointSale = null;
if ($idPointSale) {

+ 57
- 0
backend/forms/AdminSettingsForm.php View File

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

namespace backend\forms;

use common\logic\Setting\SettingModule;
use yii\base\Model;

class AdminSettingsForm extends Model
{
public function __get($name)
{
return SettingModule::getInstance()->getAdminSettingBag()->get($name);
}

public function __set($name, $value)
{
$this->$name = $value;
}

public function rules()
{
$rulesArray = [];
$typesArray = [
'string' => ['string', 'text'],
'date' => ['date'],
'boolean' => ['boolean'],
'integer' => ['integer'],
'double' => ['double', 'float'],
];
foreach($typesArray as $rule => $typesSettingArray) {
$rulesArray[] = [$this->getSettingNamesByTypeArray($typesSettingArray), $rule];
}
return $rulesArray;
}

public function attributeLabels()
{
$attributeLabelsArray = [];
foreach(SettingModule::getInstance()->getAdminSettingDefinition()->getSettingDetailsFlat() as $settingDetail) {
$attributeLabelsArray[$settingDetail->getName()] = $settingDetail->getLabel();
}
return $attributeLabelsArray;
}

public function getSettingNamesByTypeArray(array $typesSettingArray): array
{
$settingNamesTypeArray = [];
foreach(SettingModule::getInstance()->getAdminSettingDefinition()->getSettingDetailsFlat() as $settingDetail) {
if(in_array($settingDetail->getType(), $typesSettingArray)) {
$settingNamesTypeArray[] = $settingDetail->getName();
}
}
return $settingNamesTypeArray;
}
}

?>

+ 13
- 28
backend/views/distribution/shopping-cart-labels.php View File

@@ -1,35 +1,20 @@
<?php

use common\logic\Order\Order\Module\OrderModule;
use yii\helpers\Html;
use common\logic\Distribution\Distribution\Export\DistributionShoppingCartLabelsPdfGenerator;

$orderModule = OrderModule::getInstance();
$distributionShoppingCartLabelsPdfGenerator = DistributionShoppingCartLabelsPdfGenerator::getInstance();
$index = 0;

?>
<?php
$i = 0;
foreach($ordersArray as $key => $order):
foreach($ordersArray as $key => $order) {
$index ++;
?>
<div class="shopping-cart-label">
<div class="inner">
<div class="username">
<?= $orderModule->getOrderUsername($order); ?>
</div>
<div class="point-sale">
<?= date('d/m', strtotime($distribution->date)); ?> &bull;
<?= Html::encode($order->pointSale->name); ?>
</div>
<div class="products">
<?= $orderModule->getCartSummary($order); ?>
</div>
</div>
</div>
<?php if($index == $shoppingCartLabelsPerColumn) {

echo $distributionShoppingCartLabelsPdfGenerator->getShoppingCartLabelAsHtml($order);

if($index == $shoppingCartLabelsPerColumn) {
$index = 0;
} ?>
<?php if($index == 0 && $key !== array_key_last($ordersArray)): ?>
<columnbreak />
<?php endif; ?>
<?php endforeach; ?>
}
if(!$isSpecificFormat && $index == 0 && $key !== array_key_last($ordersArray)) {
echo '<columnbreak />';
}
}
?>

+ 10
- 1
backend/views/feature-admin/index.php View File

@@ -50,6 +50,15 @@ $this->addBreadcrumb($this->getTitle());
<?= GridView::widget([
'dataProvider' => $dataProviderFeatures,
'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',
[
'attribute' => 'status',
@@ -121,7 +130,7 @@ $this->addBreadcrumb($this->getTitle());
'format' => 'raw',
'value' => function ($model) {
if($model->is_paid_feature && $model->price) {
return Price::format($model->price);
return Price::format($model->price, 0);
}

return '';

+ 8
- 1
backend/views/feature-admin/update.php View File

@@ -49,7 +49,14 @@ $this->addBreadcrumb('Modifier') ;
<?php $form = ActiveForm::begin(); ?>
<?= $form->field($model, 'status')->radioList([1 => 'Oui', 0 => 'Non']) ?>
<?= $form->field($model, 'name') ?>
<?= $form->field($model, 'description')->textarea(['rows' => '4']) ; ?>
<?= $form->field($model, 'description')->widget(letyii\tinymce\Tinymce::class, [
'options' => [
'id' => 'testid',
],
'configs' => [ // Read more: https://www.tiny.cloud/docs/tinymce/6/full-featured-open-source-demo/
'plugins' => 'preview importcss searchreplace autolink autosave save directionality code visualblocks visualchars fullscreen image link media template codesample table charmap pagebreak nonbreaking anchor insertdatetime advlist lists wordcount help charmap quickbars emoticons accordion' ,
]
]) ; ?>
<?= $form->field($model, 'only_for_selected_producers')->radioList([1 => 'Oui', 0 => 'Non']) ?>
<?= $form->field($model, 'is_paid_feature')->radioList([1 => 'Oui', 0 => 'Non']) ?>
<?= $form->field($model, 'price') ?>

+ 4
- 3
backend/views/layouts/left.php View File

@@ -37,14 +37,14 @@
*/

use common\helpers\GlobalParam;
use common\logic\Feature\Feature\Model\Feature;
use common\logic\Feature\Feature\Feature;
use common\logic\User\User\Module\UserModule;

$producerModule = $this->getProducerModule();
$userModule = UserModule::getInstance();
$userProducerModule = $this->getUserProducerModule();
$ticketModule = $this->getTicketModule();
$featureManager = $this->getFeatureModule()->getManager();
$featureChecker = $this->getFeatureModule()->getChecker();

$producer = GlobalParam::getCurrentProducer();
$userCurrent = GlobalParam::getCurrentUser();
@@ -106,7 +106,7 @@ $isUserCurrentGrantedAsProducer = $userModule->getAuthorizationChecker()->isGran
'items' => [
['label' => 'Liste', 'icon' => 'th-list', 'url' => ['/product/index'], 'visible' => $isUserCurrentGrantedAsProducer],
['label' => 'Catégories', 'icon' => 'book', 'url' => ['/product-category/index'], 'visible' => $isUserCurrentGrantedAsProducer],
['label' => 'Import prix', 'icon' => 'upload', 'url' => ['/product/price-import'], 'visible' => $isUserCurrentGrantedAsProducer && $featureManager->isEnabled(Feature::ALIAS_PRODUCT_PRICE_IMPORT)],
['label' => 'Import prix', 'icon' => 'upload', 'url' => ['/product/price-import'], 'visible' => $isUserCurrentGrantedAsProducer && $featureChecker->isEnabled(Feature::ALIAS_PRODUCT_PRICE_IMPORT)],
]
],
['label' => 'Points de vente', 'icon' => 'map-marker', 'url' => ['/point-sale/index'], 'visible' => $isUserCurrentGrantedAsProducer, 'active' => Yii::$app->controller->id == 'point-sale'],
@@ -182,6 +182,7 @@ $isUserCurrentGrantedAsProducer = $userModule->getAuthorizationChecker()->isGran
['label' => 'Commandes clients', 'icon' => 'calendar', 'url' => ['/stats-admin/customer-orders'], 'visible' => $isUserCurrentGrantedAsAdministrator],
],
],
['label' => 'Paramètres', 'icon' => 'cog', 'url' => ['/setting-admin/index'], 'visible' => $isUserCurrentGrantedAsAdministrator && $featureChecker->isEnabled(Feature::ALIAS_SETTINGS)],
['label' => 'Fonctionnalités', 'icon' => 'flag', 'url' => ['/feature-admin/index'], 'visible' => $isUserCurrentGrantedAsAdministrator],
['label' => 'Tranches de prix', 'icon' => 'eur', 'url' => ['/producer-price-range-admin/index'], 'visible' => $isUserCurrentGrantedAsAdministrator],
['label' => 'Taxes', 'icon' => 'eur', 'url' => ['/tax-rate-admin/index'], 'visible' => $isUserCurrentGrantedAsAdministrator],

+ 2
- 0
backend/views/point-sale/_form.php View File

@@ -88,6 +88,8 @@ $distributionModule = DistributionModule::getInstance();
->dropDownList( ProductPrice::percentValues(), [])->hint('Pourcentage appliqué aux prix de chaque produit dans ce point de vente.');*/ ?>

<?= $form->field($model, 'maximum_number_orders')->textInput() ?>
<?= $form->field($model, 'minimum_order_amount')->textInput() ?>


<div id="delivery-days">
<h2>Jours de livraison</h2>

+ 41
- 17
backend/views/producer/update.php View File

@@ -39,6 +39,9 @@
use common\helpers\Dropdown;
use common\helpers\GlobalParam;
use common\logic\Distribution\Distribution\Module\DistributionModule;
use common\logic\Distribution\Distribution\Service\ExportManager;
use common\logic\Feature\Feature\Feature;
use common\logic\Feature\Feature\FeatureModule;
use common\logic\User\User\Module\UserModule;
use common\logic\User\UserGroup\Module\UserGroupModule;
use yii\helpers\Html;
@@ -53,6 +56,7 @@ use yii\helpers\ArrayHelper;
$userModule = UserModule::getInstance();
$userGroupModule = UserGroupModule::getInstance();
$distributionExportManager = DistributionModule::getInstance()->getExportManager();
$featureChecker = FeatureModule::getInstance()->getChecker();

$userCurrent = GlobalParam::getCurrentUser();

@@ -304,8 +308,13 @@ $this->addBreadcrumb($this->getTitle());
->dropDownList(Dropdown::noYesChoices()); ?>
<?= $form->field($model, 'option_csv_export_by_piece')
->dropDownList(Dropdown::noYesChoices()); ?>
<?= $form->field($model, 'export_shopping_cart_labels_number_per_column')
->dropDownList(Dropdown::numberChoices(1, 20)); ?>
<?php if($featureChecker->isEnabled(Feature::ALIAS_EXPORT_SHOPPING_CART_LABELS_ADVANCED)): ?>
<?= $form->field($model, 'export_shopping_cart_labels_format')
->dropDownList($distributionExportManager->getGenerator(ExportManager::SHOPPING_CART_LABELS_PDF)->populateDropdownSpecificFormats()); ?>
<?php else: ?>
<?= $form->field($model, 'export_shopping_cart_labels_number_per_column')
->dropDownList(Dropdown::numberChoices(1, 20)); ?>
<?php endif; ?>
</div>
</div>

@@ -350,21 +359,27 @@ $this->addBreadcrumb($this->getTitle());
<?= $form->field($model, 'option_check_by_default_prevent_user_credit')
->dropDownList(Dropdown::noYesChoices()); ?>

<h4>Paiement en ligne</h4>
<?= $form->field($model, 'online_payment')
->dropDownList(Dropdown::noYesChoices()); ?>
<?= $form->field($model, 'option_online_payment_minimum_amount')
->hint('Valeur par défaut si non défini : ' . Producer::ONLINE_PAYMENT_MINIMUM_AMOUNT_DEFAULT . ' €')
->textInput(); ?>
<?= $form->field($model, 'option_stripe_mode_test')->dropDownList(Dropdown::noYesChoices()); ?>
<?= $form->field($model, 'option_online_payment_type')
->dropDownList([
'credit' => 'Alimentation du crédit',
'order' => 'Paiement à la commande',
], []); ?>
<?= $form->field($model, 'option_stripe_public_key')->textInput(); ?>
<?= $form->field($model, 'option_stripe_private_key')->textInput(); ?>
<?= $form->field($model, 'option_stripe_endpoint_secret')->textInput(); ?>
<?php if($featureChecker->isEnabled(Feature::ALIAS_ONLINE_PAYMENT)): ?>
<h4>Paiement en ligne</h4>
<?php if($userModule->getAuthorizationChecker()->isGrantedAsAdministrator($userCurrent)): ?>
<?= $form->field($model, 'online_payment')
->dropDownList(Dropdown::noYesChoices()); ?>

<?= $form->field($model, 'option_stripe_mode_test')->dropDownList(Dropdown::noYesChoices()); ?>
<?= $form->field($model, 'option_online_payment_type')
->dropDownList([
'credit' => 'Alimentation du crédit',
'order' => 'Paiement à la commande',
], []); ?>
<?= $form->field($model, 'option_stripe_public_key')->textInput(); ?>
<?= $form->field($model, 'option_stripe_private_key')->textInput(); ?>
<?= $form->field($model, 'option_stripe_endpoint_secret')->textInput(); ?>
<?php endif; ?>

<?= $form->field($model, 'option_online_payment_minimum_amount')
->hint('Valeur par défaut si non défini : ' . Producer::ONLINE_PAYMENT_MINIMUM_AMOUNT_DEFAULT . ' €')
->textInput(); ?>
<?php endif; ?>
</div>
</div>

@@ -431,6 +446,15 @@ $this->addBreadcrumb($this->getTitle());
->hint("Affichées juste en dessous de l'adresse"); ?>
<?= $form->field($model, 'document_infos_bottom')
->textarea(['rows' => 8]) ?>

<?= $form->field($model, 'document_image_bottomFile')->fileInput()->hint('') ?>
<?php
if (strlen($model->document_image_bottom)) {
echo '<img src="' . Yii::$app->urlManagerProducer->getHostInfo() . '/' . Yii::$app->urlManagerProducer->baseUrl . '/uploads/' . $model->document_image_bottom . '" width="400px" /><br />';
echo '<input type="checkbox" name="delete_document_image_bottom" id="delete_document_image_bottom" /> <label for="delete_document_image_bottom">Supprimer l\'image</label><br /><br />';
}
?>

<?= $form->field($model, 'document_infos_quotation')
->textarea(['rows' => 8]) ?>
<?= $form->field($model, 'document_infos_invoice')

+ 112
- 0
backend/views/setting-admin/index.php View File

@@ -0,0 +1,112 @@
<?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.
*/

use common\logic\Setting\SettingModule;
use lo\widgets\Toggle;
use yii\bootstrap\ActiveForm;
use yii\helpers\Html;

\backend\assets\VuejsSettingFormAsset::register($this);

$settingModule = SettingModule::getInstance();
$adminSettingDefinition = $settingModule->getAdminSettingDefinition();

$this->setTitle('Paramètres');
$this->addBreadcrumb($this->getTitle());

?>

<script>
var appInitValues = {
sectionsArray: <?php echo json_encode($adminSettingDefinition->getSectionsArray()); ?>,
};
</script>

<div class="setting-admin-index setting-form" id="app-setting-admin">

<div id="nav-params">
<a v-for="section in sectionsArray" :class="'btn '+((currentSection == section.name) ? 'btn-primary' : 'btn-default')"
@click="changeSection(section)" :href="'#'+section.name">
{{ section.nameDisplay }}
</a>
</div>

<?php $form = ActiveForm::begin(); ?>
<?php foreach($adminSettingDefinition->getSettingDetails() as $sectionName => $sectionsArray): ?>
<div v-show="currentSection == '<?= $sectionName ?>'" class="panel panel-default">
<div class="panel-body">
<?php foreach($sectionsArray as $subSectionName => $subSectionsArray): ?>
<h4><?php echo $adminSettingDefinition->getSectionLabelBySectionName($subSectionName); ?></h4>
<?php foreach($subSectionsArray as $settingDetail): ?>
<?php echo field($form, $model, $settingDetail); ?>
<?php endforeach; ?>
<?php endforeach; ?>
</div>
</div>
<?php endforeach; ?>
<div class="form-group">
<?= Html::submitButton('Sauvegarder', ['class' => 'btn btn-primary']) ?>
</div>
<?php ActiveForm::end(); ?>
</div>

<?php

function field($form, $model, $settingDetail) {
$field = $form->field($model, $settingDetail->getName());
if($settingDetail->getFormType() == 'checkbox') {
return $field->checkbox();
}
elseif($settingDetail->getFormType() == 'toggle') {
return $form->field($model, $settingDetail->getName(), ['options' => ['class' => 'form-group form-toggle']])->widget(Toggle::class, ['options' => ['data-on' => 'Oui', 'data-off' => 'Non', 'data-offstyle' => 'default']]);
}
elseif($settingDetail->getFormType() == 'select') {
return $field->dropDownList($settingDetail->getOptions());
}
elseif($settingDetail->getFormType() == 'textarea') {
return $field->textarea(['rows' => 4]);
}
elseif($settingDetail->getFormType() == 'input') {
return $field->textInput();
}
else {
return '<div class="form-group"><span class="glyphicon glyphicon-alert"></span> Type de champ non défini pour le paramètre "'.$settingDetail->getName().'"</div>';
}
}

?>

+ 7
- 2
backend/views/support/index.php View File

@@ -41,7 +41,12 @@ use common\logic\Ticket\Ticket\Module\TicketModule;
use yii\helpers\Html;
use yii\grid\GridView;

/**
* @var $this common\components\ViewBackend
*/

$ticketModule = TicketModule::getInstance();
$adminSettingBag = $this->getSettingModule()->getAdminSettingBag();
$userCurrent = $this->getUserCurrent();
$this->setTitle('Support & contact');
$this->addBreadcrumb($this->getTitle());
@@ -62,7 +67,7 @@ $this->addBreadcrumb($this->getTitle());
<span class="info-box-text">Me contacter directement</span>
<span class="info-box-text">
<br/>
<strong><?= Yii::$app->parameterBag->get('adminPhoneNumber'); ?></strong>
<strong><?= $adminSettingBag->get('administratorPhoneNumber'); ?></strong>
</span>
</div>
</div>
@@ -89,7 +94,7 @@ $this->addBreadcrumb($this->getTitle());
<div class="info-box">
<span class="info-box-icon bg-yellow"><i class="fa fa-envelope"></i></span>
<div class="info-box-content">
<span class="info-box-text"><br/><?= Html::a("M'envoyer un email", 'mailto:'.Yii::$app->parameterBag->get('adminEmail'), ['class' => 'btn btn-sm btn-default']); ?></span>
<span class="info-box-text"><br/><?= Html::a("M'envoyer un email", 'mailto:'.$adminSettingBag->get('administratorEmail'), ['class' => 'btn btn-sm btn-default']); ?></span>
</div>
</div>
</div>

+ 44
- 1
backend/web/css/screen.css View File

@@ -428,7 +428,7 @@ a.btn.btn-primary .glyphicon-triangle-bottom, button.btn.btn-primary .glyphicon-

/* line 344, ../sass/screen.scss */
#nav-params {
margin-bottom: 30px;
margin-bottom: 20px;
}
/* line 347, ../sass/screen.scss */
#nav-params a {
@@ -2802,6 +2802,49 @@ termes.
width: 100px;
}

/* line 4, ../sass/setting/_form.scss */
.setting-form .panel h4 {
font-size: 23px;
margin-bottom: 20px;
text-transform: uppercase;
border-bottom: solid 1px gray;
}
/* line 10, ../sass/setting/_form.scss */
.setting-form .panel h4:not(:first-child) {
margin-top: 45px;
}
/* line 16, ../sass/setting/_form.scss */
.setting-form .form-group.has-success, .setting-form .form-group.has-success label {
color: #333;
}
/* line 21, ../sass/setting/_form.scss */
.setting-form .form-group .checkbox input {
position: relative;
top: -1px;
margin-right: 3px;
}
/* line 30, ../sass/setting/_form.scss */
.setting-form .form-group.form-toggle .control-label {
position: relative;
top: 7px;
left: 10px;
}
/* line 36, ../sass/setting/_form.scss */
.setting-form .form-group.form-toggle .toggle {
-moz-border-radius: 20px;
-webkit-border-radius: 20px;
border-radius: 20px;
float: left;
}
/* line 41, ../sass/setting/_form.scss */
.setting-form .form-group.form-toggle .toggle .toggle-group .btn.toggle-on {
color: white;
}
/* line 45, ../sass/setting/_form.scss */
.setting-form .form-group.form-toggle .toggle .toggle-group .btn.toggle-off {
border-color: white;
}

/**
Copyright distrib (2018)


+ 7
- 1
backend/web/js/backend.js View File

@@ -464,7 +464,13 @@ function opendistrib_features_index() {
.then(function (response) {
appAlerts.alertResponse(response);
});
})
});

opendistrib_sortable_list(
'.feature-admin-index',
'.btn-position',
'feature-admin/position'
);
}

function opendistrib_datepicker() {

+ 63
- 0
backend/web/js/vuejs/setting-form.js View File

@@ -0,0 +1,63 @@

/**
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.
*/

var app = new Vue({
el: '#app-setting-admin',
data() {
return Object.assign({
currentSection: null,
sectionsArray: []
}, window.appInitValues);
},
methods: {
changeSection: function(section) {
this.currentSection = section.name ;
},
getInitialSection: function() {
var hash = window.location.hash.substring(1);
if(hash && hash.length) {
return hash;
}
return this.sectionsArray[0].name;
}
},
mounted: function() {
this.currentSection = this.getInitialSection();
}
});



+ 2
- 1
backend/web/sass/screen.scss View File

@@ -342,7 +342,7 @@ a.btn, button.btn {
}

#nav-params {
margin-bottom: 30px ;
margin-bottom: 20px ;

a {
margin-right: 10px ;
@@ -1535,4 +1535,5 @@ a.btn, button.btn {
@import "support/_view.scss";
@import "producer-admin/_index.scss";
@import "feature-admin/_index.scss";
@import "setting/_form.scss";
@import "_responsive.scss" ;

+ 52
- 0
backend/web/sass/setting/_form.scss View File

@@ -0,0 +1,52 @@

.setting-form {
.panel {
h4 {
font-size: 23px;
margin-bottom: 20px;
text-transform: uppercase;
border-bottom: solid 1px gray;
}
h4:not(:first-child) {
margin-top: 45px;
}
}

.form-group {
&.has-success, &.has-success label {
color: #333;
}

.checkbox {
input {
position: relative;
top: -1px;
margin-right: 3px;
}
}

&.form-toggle {

.control-label {
position: relative;
top: 7px;
left: 10px;
}

.toggle {
@include border-radius(20px);
float: left;

.toggle-group {
.btn.toggle-on {
//border-color: white;
color: white;
}
.btn.toggle-off {
border-color: white;
}
}
}
}
}
}

+ 2
- 0
common/assets/CommonAsset.php View File

@@ -62,6 +62,7 @@ class CommonAsset extends \common\components\MyAssetBundle
$this->addAsset('css','js/jquery-ui-1.11.4.custom/jquery-ui.min.css');
$this->addAsset('css','js/jquery-ui-1.11.4.custom/jquery-ui.theme.css');
$this->addAsset('css','js/vuejs/vcalendar/vcalendar.min.css') ;
$this->addAsset('css','js/simple-lightbox/simpleLightbox.min.css') ;
$this->addAsset('css','css/screen.css') ;
// js
@@ -69,6 +70,7 @@ class CommonAsset extends \common\components\MyAssetBundle
$this->addAsset('js','js/jquery-ui-1.11.4.custom/jquery-ui.min.js');
$this->addAsset('js','js/promise-polyfill/promise.min.js');
$this->addAsset('js','js/axios/axios.min.js');
$this->addAsset('js','js/simple-lightbox/simpleLightbox.min.js') ;
$this->addAsset('js','js/vuejs/vue.js');

// Documentation : https://vcalendar.io/

+ 2
- 1
common/components/BusinessLogic.php View File

@@ -15,6 +15,7 @@ class BusinessLogic
public function getModules()
{
return [
$this->getSettingModule(),
$this->getFeatureModule(),
$this->getFeatureProducerModule(),
$this->getUnitModule(),
@@ -54,7 +55,7 @@ class BusinessLogic
return $this->producerContext;
}

public function setProducerContext(Producer $producer)
public function setProducerContext(Producer $producer = null)
{
$this->producerContext = $producer;


+ 8
- 2
common/components/BusinessLogicTrait.php View File

@@ -11,8 +11,8 @@ use common\logic\Document\DeliveryNote\Module\DeliveryNoteModule;
use common\logic\Document\Document\Module\DocumentModule;
use common\logic\Document\Invoice\Module\InvoiceModule;
use common\logic\Document\Quotation\Module\QuotationModule;
use common\logic\Feature\Feature\Module\FeatureModule;
use common\logic\Feature\FeatureProducer\Module\FeatureProducerModule;
use common\logic\Feature\Feature\FeatureModule;
use common\logic\Feature\FeatureProducer\FeatureProducerModule;
use common\logic\Opinion\Module\OpinionModule;
use common\logic\Order\Order\Module\OrderModule;
use common\logic\Order\ProductOrder\Module\ProductOrderModule;
@@ -25,6 +25,7 @@ use common\logic\Product\Product\Module\ProductModule;
use common\logic\Product\ProductCategory\Module\ProductCategoryModule;
use common\logic\Product\ProductPointSale\Module\ProductPointSaleModule;
use common\logic\Product\ProductPrice\Module\ProductPriceModule;
use common\logic\Setting\SettingModule;
use common\logic\Subscription\ProductSubscription\Module\ProductSubscriptionModule;
use common\logic\Subscription\Subscription\Module\SubscriptionModule;
use common\logic\Ticket\Ticket\Module\TicketModule;
@@ -196,4 +197,9 @@ trait BusinessLogicTrait
{
return FeatureProducerModule::getInstance();
}

public function getSettingModule(): SettingModule
{
return SettingModule::getInstance();
}
}

+ 2
- 1
common/components/DolibarrApi.php View File

@@ -13,7 +13,8 @@ class DolibarrApi extends AbstractApi
public function createInvoice(int $idUser)
{
return $this->post(self::RESOURCE_INVOICES, [
'socid' => $idUser
'socid' => $idUser,
'cond_reglement_id' => 2
]);
}


+ 1
- 1
common/config/params.php View File

@@ -37,7 +37,7 @@
*/

return [
'version' => '23.10.E',
'version' => '23.11.B',
'maintenanceMode' => false,
'siteName' => 'Opendistrib',
'adminEmail' => 'contact@opendistrib.net',

+ 8
- 0
common/logic/AbstractChecker.php View File

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

namespace common\logic;

abstract class AbstractChecker extends AbstractService implements CheckerInterface
{

}

+ 8
- 1
common/logic/AbstractRepository.php View File

@@ -67,7 +67,9 @@ abstract class AbstractRepository extends AbstractService implements RepositoryI
public function defaultFilterProducerContext(): void
{
$defaultOptions = $this->getDefaultOptionsSearch();
if(isset($defaultOptions['attribute_id_producer']) && $defaultOptions['attribute_id_producer']) {
if(isset($defaultOptions['attribute_id_producer'])
&& $defaultOptions['attribute_id_producer']
&& $this->getProducerContext()) {
$this->query->andWhere([$defaultOptions['attribute_id_producer'] => $this->getProducerContextId()]);
}
}
@@ -89,4 +91,9 @@ abstract class AbstractRepository extends AbstractService implements RepositoryI
{
return $this->createQuery();
}

public function queryDefaultAll()
{
return $this->createDefaultQuery();
}
}

+ 8
- 0
common/logic/AbstractResolver.php View File

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

namespace common\logic;

abstract class AbstractResolver extends AbstractService implements ResolverInterface
{

}

+ 1
- 0
common/logic/AbstractService.php View File

@@ -15,6 +15,7 @@ abstract class AbstractService extends AbstractSingleton implements ServiceInter
SolverInterface::class,
RepositoryQueryInterface::class,
RepositoryInterface::class,
CheckerInterface::class,
NotifierInterface::class,
BuilderInterface::class,
ResolverInterface::class,

+ 8
- 0
common/logic/CheckerInterface.php View File

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

namespace common\logic;

interface CheckerInterface
{

}

+ 120
- 29
common/logic/Distribution/Distribution/Export/DistributionShoppingCartLabelsPdfGenerator.php View File

@@ -4,34 +4,81 @@ namespace common\logic\Distribution\Distribution\Export;

use common\logic\AbstractGenerator;
use common\logic\Distribution\Distribution\Model\Distribution;
use common\logic\Feature\Feature\Feature;
use common\logic\Feature\Feature\FeatureChecker;
use common\logic\Feature\Feature\FeatureManager;
use common\logic\Order\Order\Model\Order;
use common\logic\Order\Order\Repository\OrderRepository;
use common\logic\Order\Order\Service\OrderSolver;
use common\logic\Producer\Producer\Service\ProducerSolver;
use kartik\mpdf\Pdf;
use yii\base\ErrorException;
use yii\helpers\BaseStringHelper;
use yii\helpers\Html;

class DistributionShoppingCartLabelsPdfGenerator extends AbstractGenerator implements DistributionExportGeneratorInterface
{
const FORMAT_70_42 = '70x42';

protected ProducerSolver $producerSolver;
protected OrderRepository $orderRepository;
protected OrderSolver $orderSolver;
protected FeatureChecker $featureChecker;

public function loadDependencies(): void
{
$this->producerSolver = $this->loadService(ProducerSolver::class);
$this->orderRepository = $this->loadService(OrderRepository::class);
$this->orderSolver = $this->loadService(OrderSolver::class);
$this->featureChecker = $this->loadService(FeatureChecker::class);
}

public function getSpecificFormatDetailsArray(): array
{
return [
self::FORMAT_70_42 => [70, 42]
];
}

public function generate(Distribution $distribution, bool $save = false)
{
$isSpecificFormat = false;
$exportShoppingCartLabelsFormat = $this->producerSolver->getConfig('export_shopping_cart_labels_format');
if($this->featureChecker->isEnabled(Feature::ALIAS_EXPORT_SHOPPING_CART_LABELS_ADVANCED)) {
if($exportShoppingCartLabelsFormat) {
$isSpecificFormat = true;
[$specificFormatWidth, $specificFormatHeight] = $this->getSpecificFormatDetails($exportShoppingCartLabelsFormat);
}
else {
throw new ErrorException("Aucun format d'étiquette n'est défini dans les paramètres.");
}
}

$ordersArray = $this->orderRepository->findOrdersByDistribution($distribution);
$ordersArray = $this->filterOrdersExcludedUsersAndPointSales($ordersArray);

$content = \Yii::$app->getView()->render('@backend/views/distribution/shopping-cart-labels.php', [
'distribution' => $distribution,
'ordersArray' => $ordersArray,
'isSpecificFormat' => $isSpecificFormat,
'shoppingCartLabelsPerColumn' => $this->producerSolver->getConfig('export_shopping_cart_labels_number_per_column') ?: 8
]);

$pdf = new Pdf([
if($isSpecificFormat) {
$pdf = $this->getPdf($distribution, $content, true, $specificFormatWidth, $specificFormatHeight);
}
else {
$pdf = $this->getPdf($distribution, $content, false);
$pdf->getApi()->SetColumns(4);
$pdf->getApi()->keepColumns = true;
}

return $pdf->render();
}

public function getPdf(Distribution $distribution, string $content, bool $isSpecificFormat, float $specificFormatWidth = 0, float $specificFormatHeight = 0)
{
return new Pdf([
'mode' => Pdf::MODE_UTF8,
'format' => Pdf::FORMAT_A4,
'orientation' => Pdf::ORIENT_PORTRAIT,
@@ -40,17 +87,8 @@ class DistributionShoppingCartLabelsPdfGenerator extends AbstractGenerator imple
'@app/web/pdf/Etiquettes-' . $distribution->date . '-' . $this->getProducerContextId() . '.pdf'
),
'content' => $content,
'cssInline' => $this->getCss(),
'methods' => [
'SetHeader' => ['Étiquettes du ' . date('d/m/Y', strtotime($distribution->date))],
'SetFooter' => ['{PAGENO}'],
],
'cssInline' => $this->getCss($isSpecificFormat, $specificFormatWidth, $specificFormatHeight),
]);

$pdf->getApi()->SetColumns(4);
$pdf->getApi()->keepColumns = true;

return $pdf->render();
}

public function filterOrdersExcludedUsersAndPointSales(array $ordersArray)
@@ -77,9 +115,9 @@ class DistributionShoppingCartLabelsPdfGenerator extends AbstractGenerator imple
return $order->pointSale && $order->pointSale->exclude_export_shopping_cart_labels;
}

public function getCss(): string
public function getCss(bool $isSpecificFormat = false, float $specificFormatWith = 0, float $specificFormatHeight = 0): string
{
return '
$css = '
@page {
margin: 0px 0px 0px 0px !important;
padding: 0px 0px 0px 0px !important;
@@ -90,21 +128,6 @@ class DistributionShoppingCartLabelsPdfGenerator extends AbstractGenerator imple
font-size: 13px;
}
.clr {
clear: both;
}
.shopping-cart-label {
-webkit-column-break-inside:avoid;
column-break-inside: avoid;
-webkit-page-break-inside:avoid;
page-break-inside: avoid;
}
.shopping-cart-label .inner {
padding: 8px;
}
.shopping-cart-label .username {
font-weight: bold;
font-size: 13px;
@@ -119,7 +142,75 @@ class DistributionShoppingCartLabelsPdfGenerator extends AbstractGenerator imple
.shopping-cart-label .products {
font-size: 10px;
}';

if($isSpecificFormat) {

$paddingShoppingCartLabel = 3;
$specificFormatWith = $specificFormatWith - 2 * $paddingShoppingCartLabel;
$specificFormatHeight = $specificFormatHeight - 2 * $paddingShoppingCartLabel;

$css .= '
.shopping-cart-label {
box-sizing: border-box;
padding: '.$paddingShoppingCartLabel.'mm;
width: '.$specificFormatWith.'mm;
height: '.$specificFormatHeight.'mm;
display: block;
float: left;
}';
}
else {
$css .= '
.shopping-cart-label {
-webkit-column-break-inside:avoid;
column-break-inside: avoid;
-webkit-page-break-inside:avoid;
page-break-inside: avoid;
}
';
.shopping-cart-label .inner {
padding: 8px;
}
';
}

return $css;
}

public function getShoppingCartLabelAsHtml(Order $order): string
{
return '<div class="shopping-cart-label">
<div class="inner">
<div class="username">
'.$this->orderSolver->getOrderUsername($order).'
</div>
<div class="point-sale">
'.date('d/m', strtotime($order->distribution->date)).' &bull;
'.$order->pointSale->name.'
</div>
<div class="products">
'.$this->orderRepository->getCartSummary($order).'
</div>
</div>
</div>';
}

public function getSpecificFormatDetails(string $name): array
{
$specificFormatDetailsArray = $this->getSpecificFormatDetailsArray();
if(!isset($specificFormatDetailsArray[$name])) {
throw new ErrorException("Format d'étiquette inconnu");
}
return $specificFormatDetailsArray[$name];
}

public function populateDropdownSpecificFormats(): array
{
$specificFormatsArray = [null => '--'];
foreach($this->getSpecificFormatDetailsArray() as $key => $specificFormat) {
$specificFormatsArray[$key] = $specificFormat[0].'x'.$specificFormat[1].' mm';
}
return $specificFormatsArray;
}
}

+ 7
- 1
common/logic/Document/Document/Service/DocumentManager.php View File

@@ -52,7 +52,13 @@ class DocumentManager extends AbstractManager
]);

$contentFooter = '<div id="footer">';
$contentFooter .= '<div class="infos-bottom">' . Html::encode($producer->document_infos_bottom) . '</div>';
if ($this->producerSolver->getConfig('document_image_bottom')) {
$urlDocumentImageBottom = \Yii::$app->urlManagerProducer->getHostInfo() . '/' . \Yii::$app->urlManagerProducer->baseUrl . '/uploads/' . $producer->document_image_bottom;
$contentFooter .= '<div class="image"><img src="'.$urlDocumentImageBottom.'" style="max-height:80px;" /></div>';
}
if ($this->producerSolver->getConfig('document_infos_bottom')) {
$contentFooter .= '<div class="infos-bottom">'.nl2br($producer->document_infos_bottom).'</div>';
}
$contentFooter .= '</div>';

$marginBottom = 10;

common/logic/Feature/Feature/Model/Feature.php → common/logic/Feature/Feature/Feature.php View File

@@ -36,17 +36,19 @@
* termes.
*/

namespace common\logic\Feature\Feature\Model;
namespace common\logic\Feature\Feature;

use common\components\ActiveRecordCommon;
use common\logic\Feature\FeatureProducer\Model\FeatureProducer;
use common\logic\Feature\FeatureProducer\FeatureProducer;
use yii\db\ActiveQuery;
use yii\db\Schema;

class Feature extends ActiveRecordCommon
{
const ALIAS_CONTACT = 'contact';
const ALIAS_PRODUCT_PRICE_IMPORT = 'product_price_import';
const ALIAS_ONLINE_PAYMENT = 'online_payment';
const ALIAS_EXPORT_SHOPPING_CART_LABELS_ADVANCED = 'export_shopping_cart_labels_advanced';
const ALIAS_SETTINGS = 'settings';

/**
* @inheritdoc
@@ -64,6 +66,7 @@ class Feature extends ActiveRecordCommon
return [
[['alias', 'name'], 'required'],
[['status', 'is_paid_feature', 'only_for_selected_producers'], 'boolean'],
[['position'], 'integer'],
[['price'], 'double'],
[['alias', 'name', 'description'], 'string'],
];
@@ -82,7 +85,8 @@ class Feature extends ActiveRecordCommon
'status' => 'Statut',
'is_paid_feature' => "Fonctionnalité payante",
'price' => 'Prix',
'only_for_selected_producers' => 'Uniquement pour les producteurs sélectionnés'
'only_for_selected_producers' => 'Uniquement pour les producteurs sélectionnés',
'position' => 'Position'
];
}


common/logic/Feature/Feature/Service/FeatureBuilder.php → common/logic/Feature/Feature/FeatureBuilder.php View File

@@ -1,10 +1,8 @@
<?php

namespace common\logic\Feature\Feature\Service;
namespace common\logic\Feature\Feature;

use common\logic\AbstractBuilder;
use common\logic\Feature\Feature\Model\Feature;
use common\logic\Feature\Feature\Repository\FeatureRepository;

class FeatureBuilder extends AbstractBuilder
{
@@ -38,7 +36,7 @@ class FeatureBuilder extends AbstractBuilder
public function createFeature(
string $alias,
string $name,
string $description,
string $description = '',
bool $status = true,
bool $isPaidFeature = false,
float $price = null

+ 48
- 0
common/logic/Feature/Feature/FeatureChecker.php View File

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

namespace common\logic\Feature\Feature;

use common\logic\AbstractChecker;
use common\logic\Feature\FeatureProducer\FeatureProducerRepository;
use yii\base\ErrorException;

class FeatureChecker extends AbstractChecker
{
protected FeatureRepository $featureRepository;
protected FeatureProducerRepository $featureProducerRepository;

public function loadDependencies(): void
{
$this->featureRepository = $this->loadService(FeatureRepository::class);
$this->featureProducerRepository = $this->loadService(FeatureProducerRepository::class);
}

public function isEnabled(string $aliasFeature): bool
{
$feature = $this->featureRepository->findOneFeatureByAlias($aliasFeature);
if(!$feature) {
throw new ErrorException("Fonctionnalité avec l'alias '".$aliasFeature."' non trouvée");
}

if(!$feature->status) {
return false;
}

$featureProducer = $this->featureProducerRepository->findOneFeatureProducer($feature);
if(!$featureProducer || is_null($featureProducer->status)) {
if($feature->is_paid_feature || $feature->only_for_selected_producers) {
return false;
}

return $feature->status;
}
else {
return $featureProducer->status;
}
}

public function isDisabled(string $aliasFeature): bool
{
return !$this->isEnabled($aliasFeature);
}
}

+ 24
- 0
common/logic/Feature/Feature/FeatureDefinition.php View File

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

namespace common\logic\Feature\Feature;

use common\logic\AbstractDefinition;

class FeatureDefinition extends AbstractDefinition
{
public function getEntityFqcn(): string
{
return Feature::class;
}

public function getFeaturesBase(): array
{
return [
Feature::ALIAS_CONTACT => 'Formulaire de contact',
Feature::ALIAS_PRODUCT_PRICE_IMPORT => 'Produits : import prix',
Feature::ALIAS_ONLINE_PAYMENT => 'Paiement en ligne',
Feature::ALIAS_EXPORT_SHOPPING_CART_LABELS_ADVANCED => "Génération d'étiquettes avec un format spécifique",
Feature::ALIAS_SETTINGS => 'Système de paramètres'
];
}
}

common/logic/Feature/Feature/Service/FeatureImporter.php → common/logic/Feature/Feature/FeatureImporter.php View File

@@ -1,6 +1,6 @@
<?php

namespace common\logic\Feature\Feature\Service;
namespace common\logic\Feature\Feature;

use common\logic\AbstractManager;

@@ -19,13 +19,10 @@ class FeatureImporter extends AbstractManager
{
$featuresBaseArray = $this->featureDefinition->getFeaturesBase();

foreach($featuresBaseArray as $alias => $featureBase) {
foreach($featuresBaseArray as $alias => $featureBaseName) {
$this->featureBuilder->createFeature(
$alias,
$featureBase['name'],
'',
$featureBase['status'],
$featureBase['is_paid_feature']
$featureBaseName
);
}
}

+ 43
- 0
common/logic/Feature/Feature/FeatureManager.php View File

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

namespace common\logic\Feature\Feature;

use common\logic\AbstractManager;
use common\logic\Feature\FeatureProducer\FeatureProducerBuilder;

class FeatureManager extends AbstractManager
{
protected FeatureBuilder $featureBuilder;
protected FeatureProducerBuilder $featureProducerBuilder;

public function loadDependencies(): void
{
$this->featureBuilder = $this->loadService(FeatureBuilder::class);
$this->featureProducerBuilder = $this->loadService(FeatureProducerBuilder::class);
}

public function enableFeature(Feature $feature): void
{
$this->featureBuilder->updateStatus($feature, true);
}

public function disableFeature(Feature $feature): void
{
$this->featureBuilder->updateStatus($feature, false);
}

public function enableFeatureForProducer(Feature $feature)
{
$this->featureProducerBuilder->updateStatusByFeature($feature, true);
}

public function disableFeatureForProducer(Feature $feature)
{
$this->featureProducerBuilder->updateStatusByFeature($feature, false);
}

public function defaultFeatureForProducer(Feature $feature)
{
$this->featureProducerBuilder->updateStatusByFeature($feature, null);
}
}

common/logic/Feature/Feature/Module/FeatureModule.php → common/logic/Feature/Feature/FeatureModule.php View File

@@ -1,13 +1,8 @@
<?php

namespace common\logic\Feature\Feature\Module;
namespace common\logic\Feature\Feature;

use common\logic\AbstractModule;
use common\logic\Feature\Feature\Repository\FeatureRepository;
use common\logic\Feature\Feature\Service\FeatureBuilder;
use common\logic\Feature\Feature\Service\FeatureDefinition;
use common\logic\Feature\Feature\Service\FeatureImporter;
use common\logic\Feature\Feature\Service\FeatureManager;

class FeatureModule extends AbstractModule
{
@@ -16,6 +11,7 @@ class FeatureModule extends AbstractModule
return [
FeatureDefinition::class,
FeatureRepository::class,
FeatureChecker::class,
FeatureBuilder::class,
FeatureImporter::class,
FeatureManager::class
@@ -32,6 +28,11 @@ class FeatureModule extends AbstractModule
return FeatureRepository::getInstance();
}

public function getChecker(): FeatureChecker
{
return FeatureChecker::getInstance();
}

public function getBuilder(): FeatureBuilder
{
return FeatureBuilder::getInstance();

common/logic/Feature/Feature/Repository/FeatureRepository.php → common/logic/Feature/Feature/FeatureRepository.php View File

@@ -1,9 +1,8 @@
<?php

namespace common\logic\Feature\Feature\Repository;
namespace common\logic\Feature\Feature;

use common\logic\AbstractRepository;
use common\logic\Feature\Feature\Model\Feature;

class FeatureRepository extends AbstractRepository
{
@@ -19,7 +18,7 @@ class FeatureRepository extends AbstractRepository
return [
self::WITH => ['featureProducers'],
self::JOIN_WITH => [],
self::ORDER_BY => '',
self::ORDER_BY => 'position ASC',
self::ATTRIBUTE_ID_PRODUCER => ''
];
}
@@ -37,4 +36,11 @@ class FeatureRepository extends AbstractRepository
->filterByAlias($alias)
->findOne();
}

public function findPaidFeatures(): array
{
return $this->createQuery()
->filterIsPaidFeature()
->find();
}
}

common/logic/Feature/Feature/Repository/FeatureRepositoryQuery.php → common/logic/Feature/Feature/FeatureRepositoryQuery.php View File

@@ -1,9 +1,8 @@
<?php

namespace common\logic\Feature\Feature\Repository;
namespace common\logic\Feature\Feature;

use common\logic\AbstractRepositoryQuery;
use common\logic\Feature\Feature\Service\FeatureDefinition;

class FeatureRepositoryQuery extends AbstractRepositoryQuery
{
@@ -19,4 +18,12 @@ class FeatureRepositoryQuery extends AbstractRepositoryQuery
$this->andWhere(['alias' => $alias]);
return $this;
}

public function filterIsPaidFeature(): self
{
$this->andWhere(['is_paid_feature' => true]);
return $this;
}


}

+ 0
- 31
common/logic/Feature/Feature/Service/FeatureDefinition.php View File

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

namespace common\logic\Feature\Feature\Service;

use common\logic\AbstractDefinition;
use common\logic\Feature\Feature\Model\Feature;

class FeatureDefinition extends AbstractDefinition
{
public function getEntityFqcn(): string
{
return Feature::class;
}

public function getFeaturesBase(): array
{
return [
Feature::ALIAS_CONTACT => $this->buildFeatureArray('Formulaire de contact', true, false),
Feature::ALIAS_PRODUCT_PRICE_IMPORT => $this->buildFeatureArray('Produits : import prix', true, false),
];
}

public function buildFeatureArray(string $name, bool $status, bool $isPaidFeature): array
{
return [
'name' => $name,
'status' => $status,
'is_paid_feature' => $isPaidFeature
];
}
}

+ 0
- 80
common/logic/Feature/Feature/Service/FeatureManager.php View File

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

namespace common\logic\Feature\Feature\Service;

use common\logic\AbstractManager;
use common\logic\Feature\Feature\Model\Feature;
use common\logic\Feature\Feature\Repository\FeatureRepository;
use common\logic\Feature\FeatureProducer\Repository\FeatureProducerRepository;
use common\logic\Feature\FeatureProducer\Service\FeatureProducerBuilder;
use yii\base\ErrorException;

class FeatureManager extends AbstractManager
{
protected FeatureRepository $featureRepository;
protected FeatureProducerRepository $featureProducerRepository;
protected FeatureBuilder $featureBuilder;
protected FeatureProducerBuilder $featureProducerBuilder;

public function loadDependencies(): void
{
$this->featureRepository = $this->loadService(FeatureRepository::class);
$this->featureProducerRepository = $this->loadService(FeatureProducerRepository::class);
$this->featureBuilder = $this->loadService(FeatureBuilder::class);
$this->featureProducerBuilder = $this->loadService(FeatureProducerBuilder::class);
}

public function isEnabled(string $aliasFeature): bool
{
$feature = $this->featureRepository->findOneFeatureByAlias($aliasFeature);
if(!$feature) {
throw new ErrorException("Fonctionnalité avec l'alias '".$aliasFeature."' non trouvée");
}

if(!$feature->status) {
return false;
}

$featureProducer = $this->featureProducerRepository->findOneFeatureProducer($feature);
if(!$featureProducer || is_null($featureProducer->status)) {
if($feature->is_paid_feature || $feature->only_for_selected_producers) {
return false;
}

return $feature->status;
}
else {
return $featureProducer->status;
}
}

public function isDisabled(string $aliasFeature): bool
{
return !$this->isEnabled($aliasFeature);
}

public function enable(Feature $feature): void
{
$this->featureBuilder->updateStatus($feature, true);
}

public function disable(Feature $feature): void
{
$this->featureBuilder->updateStatus($feature, false);
}

public function enableForProducer(Feature $feature)
{
$this->featureProducerBuilder->updateStatusByFeature($feature, true);
}

public function disableForProducer(Feature $feature)
{
$this->featureProducerBuilder->updateStatusByFeature($feature, false);
}

public function defaultForProducer(Feature $feature)
{
$this->featureProducerBuilder->updateStatusByFeature($feature, null);
}
}

common/logic/Feature/FeatureProducer/Model/FeatureProducer.php → common/logic/Feature/FeatureProducer/FeatureProducer.php View File

@@ -36,10 +36,10 @@
* termes.
*/

namespace common\logic\Feature\FeatureProducer\Model;
namespace common\logic\Feature\FeatureProducer;

use common\components\ActiveRecordCommon;
use common\logic\Feature\Feature\Model\Feature;
use common\logic\Feature\Feature\Feature;
use common\logic\Producer\Producer\Model\Producer;
use yii\db\ActiveQuery;


common/logic/Feature/FeatureProducer/Service/FeatureProducerBuilder.php → common/logic/Feature/FeatureProducer/FeatureProducerBuilder.php View File

@@ -1,12 +1,9 @@
<?php

namespace common\logic\Feature\FeatureProducer\Service;
namespace common\logic\Feature\FeatureProducer;

use common\logic\AbstractBuilder;
use common\logic\Feature\Feature\Model\Feature;
use common\logic\Feature\FeatureProducer\Model\FeatureProducer;
use common\logic\Feature\FeatureProducer\Repository\FeatureProducerRepository;
use common\logic\Producer\Producer\Model\Producer;
use common\logic\Feature\Feature\Feature;

class FeatureProducerBuilder extends AbstractBuilder
{

+ 13
- 0
common/logic/Feature/FeatureProducer/FeatureProducerDefinition.php View File

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

namespace common\logic\Feature\FeatureProducer;

use common\logic\AbstractDefinition;

class FeatureProducerDefinition extends AbstractDefinition
{
public function getEntityFqcn(): string
{
return FeatureProducer::class;
}
}

common/logic/Feature/FeatureProducer/Module/FeatureProducerModule.php → common/logic/Feature/FeatureProducer/FeatureProducerModule.php View File

@@ -1,11 +1,8 @@
<?php

namespace common\logic\Feature\FeatureProducer\Module;
namespace common\logic\Feature\FeatureProducer;

use common\logic\AbstractModule;
use common\logic\Feature\FeatureProducer\Repository\FeatureProducerRepository;
use common\logic\Feature\FeatureProducer\Service\FeatureProducerBuilder;
use common\logic\Feature\FeatureProducer\Service\FeatureProducerDefinition;

class FeatureProducerModule extends AbstractModule
{
@@ -14,7 +11,7 @@ class FeatureProducerModule extends AbstractModule
return [
FeatureProducerDefinition::class,
FeatureProducerRepository::class,
FeatureProducerBuilder::class
FeatureProducerBuilder::class,
];
}


common/logic/Feature/FeatureProducer/Repository/FeatureProducerRepository.php → common/logic/Feature/FeatureProducer/FeatureProducerRepository.php View File

@@ -1,10 +1,9 @@
<?php

namespace common\logic\Feature\FeatureProducer\Repository;
namespace common\logic\Feature\FeatureProducer;

use common\logic\AbstractRepository;
use common\logic\Feature\Feature\Model\Feature;
use common\logic\Producer\Producer\Model\Producer;
use common\logic\Feature\Feature\Feature;

class FeatureProducerRepository extends AbstractRepository
{

common/logic/Feature/FeatureProducer/Repository/FeatureProducerRepositoryQuery.php → common/logic/Feature/FeatureProducer/FeatureProducerRepositoryQuery.php View File

@@ -1,10 +1,9 @@
<?php

namespace common\logic\Feature\FeatureProducer\Repository;
namespace common\logic\Feature\FeatureProducer;

use common\logic\AbstractRepositoryQuery;
use common\logic\Feature\Feature\Model\Feature;
use common\logic\Feature\FeatureProducer\Service\FeatureProducerDefinition;
use common\logic\Feature\Feature\Feature;

class FeatureProducerRepositoryQuery extends AbstractRepositoryQuery
{
@@ -17,7 +16,7 @@ class FeatureProducerRepositoryQuery extends AbstractRepositoryQuery

public function filterByFeature(Feature $feature): self
{
$this->andWhere(['id_feature' => $feature]);
$this->andWhere(['id_feature' => $feature->id]);
return $this;
}
}

+ 0
- 25
common/logic/Feature/FeatureProducer/Service/FeatureProducerDefinition.php View File

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

namespace common\logic\Feature\FeatureProducer\Service;

use common\logic\AbstractDefinition;
use common\logic\Feature\FeatureProducer\Model\FeatureProducer;
use common\logic\Feature\FeatureProducer\Repository\FeatureProducerRepository;

class FeatureProducerDefinition extends AbstractDefinition
{
public function getEntityFqcn(): string
{
return FeatureProducer::class;
}

public function getDefinition(): FeatureProducerDefinition
{
return FeatureProducerDefinition::getInstance();
}

public function getRepository(): FeatureProducerRepository
{
return FeatureProducerRepository::getInstance();
}
}

+ 4
- 2
common/logic/Order/Order/Service/TillerManager.php View File

@@ -21,8 +21,10 @@ class TillerManager extends AbstractManager
public function loadDependencies(): void
{
$this->producerSolver = $this->loadService(ProducerSolver::class);
$this->tillerActivated = $this->producerSolver->getConfig('tiller');
$this->tillerClient = $this->getClient();
if($this->producerSolver->getProducerContext(false)) {
$this->tillerActivated = $this->producerSolver->getConfig('tiller');
$this->tillerClient = $this->getClient();
}
$this->orderSolver = $this->loadService(OrderSolver::class);
$this->orderBuilder = $this->loadService(OrderBuilder::class);
$this->productOrderSolver = $this->loadService(ProductOrderSolver::class);

+ 3
- 2
common/logic/PointSale/PointSale/Model/PointSale.php View File

@@ -86,7 +86,7 @@ class PointSale extends ActiveRecordCommon
[['id_producer', 'id_user', 'maximum_number_orders', 'status'], 'integer'],
['id_producer', 'required'],
[['users', 'users_comment', 'code'], 'safe'],
[['product_price_percent'], 'double'],
[['product_price_percent', 'minimum_order_amount'], 'double'],
];
}

@@ -129,7 +129,8 @@ class PointSale extends ActiveRecordCommon
'button_generate_delivery_note_point_sale' => 'Activer le bouton de génération de bon de livraison par point de vente',
'button_generate_delivery_note_each_user' => 'Activer le bouton de génération de bon de livraison par client',
'exclude_export_shopping_cart_labels' => "Exclure de l'export d'étiquettes",
'is_home_delivery' => "Livraison à domicile"
'is_home_delivery' => "Livraison à domicile",
'minimum_order_amount' => "Montant minimum de commande (€)"
];
}


+ 14
- 4
common/logic/Producer/Producer/Model/Producer.php View File

@@ -116,6 +116,11 @@ class Producer extends ActiveRecordCommon
*/
public $photoFile;

/**
* @var UploadedFile
*/
public $document_image_bottomFile;

/**
* @inheritdoc
*/
@@ -138,7 +143,7 @@ class Producer extends ActiveRecordCommon
return $model->tiller == true;
}
],
[['logoFile', 'photoFile'], 'file', 'extensions' => 'png, jpg, jpeg', 'mimeTypes' => 'image/png, image/jpeg'],
[['logoFile', 'photoFile', 'document_image_bottomFile'], 'file', 'extensions' => 'png, jpg, jpeg', 'mimeTypes' => 'image/png, image/jpeg'],
[
[
'order_delay',
@@ -241,6 +246,8 @@ class Producer extends ActiveRecordCommon
'option_testimony',
'contact_email',
'admin_comment',
'export_shopping_cart_labels_format',
'document_image_bottom',
],
'string'
],
@@ -290,6 +297,7 @@ class Producer extends ActiveRecordCommon
'siret',
'logo',
'photo',
'document_image_bottom',
'postcode',
'city',
'code',
@@ -339,8 +347,8 @@ class Producer extends ActiveRecordCommon
'id' => 'ID',
'name' => 'Nom',
'siret' => 'Siret',
'logo' => 'Logo',
'photo' => 'Photo',
'logoFile' => 'Logo',
'photoFile' => 'Photo',
'description' => 'Description',
'postcode' => 'Code postal',
'city' => 'Ville',
@@ -398,6 +406,7 @@ class Producer extends ActiveRecordCommon
'document_delivery_note_first_reference' => 'Première référence des bons de livraison',
'document_infos_top' => 'Informations affichées en haut des documents',
'document_infos_bottom' => 'Informations affichées en bas des documents',
'document_image_bottomFile' => "Image affichée en bas des documents",
'document_infos_quotation' => 'Informations affichées en bas des devis',
'document_infos_invoice' => 'Informations affichées en bas des factures',
'document_infos_delivery_note' => 'Informations affichées en bas des bons de livraison',
@@ -455,9 +464,10 @@ class Producer extends ActiveRecordCommon
'option_invoice_only_based_on_delivery_notes' => 'Facturer uniquement sur la base des bons de livraison',
'option_document_width_logo' => 'Largeur du logo dans les documents',
'export_shopping_cart_labels_number_per_column' => "Étiquettes (PDF) : nombre d'étiquettes par colonne",
'export_shopping_cart_labels_format' => 'Étiquettes (PDF) : format',
'option_document_display_price_unit_reference' => "Afficher les prix au kilogramme",
'id_user_group_default' => "Groupe utilisateur par défaut attribué à l'inscription",
'option_check_by_default_prevent_user_credit' => "Par défaut, prévenir l'utilisateur quand on crédite son compte"
'option_check_by_default_prevent_user_credit' => "Par défaut, prévenir l'utilisateur quand on crédite son compte",
];
}


+ 9
- 4
common/logic/ProducerContextTrait.php View File

@@ -7,18 +7,18 @@ use yii\base\ErrorException;

trait ProducerContextTrait
{
protected ?Producer $producerContext = null;
protected ?Producer $producerContext;

public function setProducerContext(Producer $producer): self
public function setProducerContext(Producer $producer = null): self
{
$this->producerContext = $producer;

return $this;
}

public function getProducerContext(): Producer
public function getProducerContext(bool $throwExceptionIfNull = true): ?Producer
{
if(is_null($this->producerContext)) {
if($throwExceptionIfNull && is_null($this->producerContext)) {
throw new ErrorException("Le contexte producteur n'est pas défini.");
}

@@ -29,4 +29,9 @@ trait ProducerContextTrait
{
return $this->getProducerContext()->id;
}

public function isOutOfProducerContext(): bool
{
return !$this->producerContext;
}
}

+ 39
- 0
common/logic/Setting/AdminSettingBag.php View File

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

namespace common\logic\Setting;

use common\logic\AbstractManager;
use common\logic\Setting\SettingBuilder;
use common\logic\Setting\SettingRepository;

class AdminSettingBag extends AbstractManager
{
protected SettingRepository $settingRepository;
protected SettingBuilder $settingBuilder;

public function loadDependencies(): void
{
$this->settingRepository = $this->loadService(SettingRepository::class);
$this->settingBuilder = $this->loadService(SettingBuilder::class);
}

public function get(string $name)
{
$setting = $this->settingRepository->findOneAdminSettingByName($name);

if($setting) {
return $setting->getValue();
}

return null;
}

public function set(string $name, $value)
{
$setting = $this->settingBuilder->createSetting($name);

if($setting) {
$this->settingBuilder->updateValue($setting, $value);
}
}
}

+ 108
- 0
common/logic/Setting/Setting.php View File

@@ -0,0 +1,108 @@
<?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\logic\Setting;

use common\components\ActiveRecordCommon;
use common\logic\Producer\Producer\Model\Producer;

class Setting extends ActiveRecordCommon
{
/**
* @inheritdoc
*/
public static function tableName()
{
return 'setting';
}

/**
* @inheritdoc
*/
public function rules()
{
return [
[['name'], 'required'],
[['id_producer'], 'integer'],
[['id_producer'], 'exist', 'skipOnError' => true, 'targetClass' => Producer::class, 'targetAttribute' => ['id_producer' => 'id']],
[['name', 'string', 'text'], 'string'],
[['name','string'], 'string', 'max' => 255],
[['date'], 'date', 'format' => 'php:Y-m-d'],
[['integer'], 'integer'],
[['float', 'double'], 'number'],
[['boolean'], 'boolean'],
[['name'], 'unique'],
];
}

/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'id' => 'ID',
'id_producer' => 'Producteur',
'name' => 'Nom',
];
}

public function getValue()
{
$settingDetail = $this->getSettingDetail();
$type = $settingDetail->getType();
return $this->$type;
}

public function getSettingDetail()
{
return SettingDefinition::getInstance()->getSettingDetailByName($this->name);
}

public function getProducer()
{
return $this->hasOne(Producer::class, ['id' => 'id_producer']);
}

public function populateProducer(Producer $producer = null): void
{
if($producer) {
$this->populateFieldObject('id_producer', 'producer', $producer);
}
}
}

+ 84
- 0
common/logic/Setting/SettingBuilder.php View File

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

namespace common\logic\Setting;

use common\logic\AbstractBuilder;
use common\logic\Producer\Producer\Model\Producer;
use yii\base\ErrorException;

class SettingBuilder extends AbstractBuilder
{
protected SettingDefinition $settingDefinition;
protected SettingRepository $settingRepository;

public function loadDependencies(): void
{
$this->settingDefinition = $this->loadService(SettingDefinition::class);
$this->settingRepository = $this->loadService(SettingRepository::class);
}

public function instanciateSetting(
string $name,
Producer $producer = null
): Setting
{
$setting = new Setting();
$setting->name = $name;
$setting->populateProducer($producer);
$this->initDefaultValue($setting);

return $setting;
}

public function createSetting(
string $name,
Producer $producer = null
): Setting
{
if($producer) {
$setting = $this->settingRepository->findOneProducerSettingByName($name);
}
else {
$setting = $this->settingRepository->findOneAdminSettingByName($name);
}

if(!$setting) {
$setting = $this->instanciateSetting($name);
$this->create($setting);
}

return $setting;
}

public function initValue(Setting $setting, $value)
{
$settingDetail = $this->settingDefinition->getSettingDetailByName($setting->name);
$type = $settingDetail->getType();
if(in_array($type, ['string', 'text', 'date', 'integer', 'float', 'double', 'boolean'])) {
$setting->$type = $value;
}
else {
throw new ErrorException("Le type de donnée du SettingDetail n'est pas reconnu.");
}
}

public function initDefaultValue(Setting $setting)
{
$settingDetail = $this->settingDefinition->getSettingDetailByName($setting->name);
if($settingDetail->getDefaultValue()) {
$this->initValue($setting, $settingDetail->getDefaultValue());
}
}

public function updateValue(Setting $setting, $value): void
{
$this->initValue($setting, $value);
$this->update($setting);
}

public function updateDefaultValueByName(Setting $setting)
{
$this->initDefaultValue($setting);
$this->update($setting);
}
}

+ 47
- 0
common/logic/Setting/SettingDefinition.php View File

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

namespace common\logic\Setting;

use common\logic\AbstractDefinition;
use common\logic\Setting\SettingDetails\Admin\AdminSettingDefinition;
use common\logic\Setting\SettingDetails\Producer\ProducerSettingDefinition;

class SettingDefinition extends AbstractDefinition
{
public function getEntityFqcn(): string
{
return Setting::class;
}

public function getSettingDetailByName(string $name)
{
$adminSettingDefinition = AdminSettingDefinition::getInstance();
$producerSettingDefinition = ProducerSettingDefinition::getInstance();

$settingDetail = $this->findSettingDetailInArray($name, $adminSettingDefinition->getSettingDetails());
if(!$settingDetail) {
$this->findSettingDetailInArray($name, $producerSettingDefinition->getSettingDetails());
}

if(!$settingDetail) {
throw new \Exception('SettingDetail non trouvé');
}

return $settingDetail;
}

public function findSettingDetailInArray(string $name, array $settingDetailsArray)
{
foreach($settingDetailsArray as $section => $subsectionsArray) {
foreach($subsectionsArray as $subsection => $settingDetailsArray) {
foreach($settingDetailsArray as $settingDetail) {
if($name == $settingDetail->getName()) {
return $settingDetail;
}
}
}
}

return null;
}
}

+ 170
- 0
common/logic/Setting/SettingDetails/AbstractSettingDetail.php View File

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

namespace common\logic\Setting\SettingDetails;

class AbstractSettingDetail
{
public string $name;
public string $label;
public string $type;
public string $section;
public ?string $formType = null;
public array $options = [];
public $defaultValue = null;
public ?string $subSection = null;
public ?string $helpMessage = null;

public function setName(string $name): self
{
$this->name = $name;
return $this;
}

public function getName(): string
{
return $this->name;
}

public function setLabel(string $label): self
{
$this->label = $label;
return $this;
}

public function getLabel(): string
{
return $this->label;
}

public function setSection(string $section): self
{
$this->section = $section;
return $this;
}

public function getSection(): string
{
return $this->section;
}

public function setSubSection(string $subSection): self
{
$this->subSection = $subSection;
return $this;
}

public function getSubSection(): ?string
{
return $this->subSection;
}

public function setHelpMessage(string $helpMessage): self
{
$this->helpMessage = $helpMessage;
return $this;
}

public function getHelpMessage(): ?string
{
return $this->helpMessage;
}

public function getFormType(): ?string
{
return $this->formType;
}

public function setFormTypeInput(): self
{
$this->formType = 'input';
return $this;
}

public function setFormTypeTextarea(): self
{
$this->formType = 'textarea';
return $this;
}

public function setFormTypeCheckbox(): self
{
$this->formType = 'checkbox';
return $this;
}

public function setFormTypeToggle(): self
{
$this->formType = 'toggle';
return $this;
}

public function setFormTypeSelect(array $options): self
{
$this->formType = 'select';
$this->options = $options;
return $this;
}

public function getOptions(): array
{
return $this->options;
}

public function setDefaultValue($defaultValue): self
{
$this->defaultValue = $defaultValue;
return $this;
}

public function getDefaultValue()
{
return $this->defaultValue;
}

public function setTypeString(): self
{
$this->type = 'string';
return $this;
}

public function setTypeText(): self
{
$this->type = 'text';
return $this;
}

public function setTypeBoolean(): self
{
$this->type = 'boolean';
return $this;
}

public function setTypeDate(): self
{
$this->type = 'date';
return $this;
}

public function setTypeInteger(): self
{
$this->type = 'integer';
return $this;
}

public function setTypeFloat(): self
{
$this->type = 'float';
return $this;
}

public function setTypeDouble(): self
{
$this->type = 'double';
return $this;
}

public function getType(): string
{
return $this->type;
}
}

+ 67
- 0
common/logic/Setting/SettingDetails/Admin/AdminSettingDefinition.php View File

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

namespace common\logic\Setting\SettingDetails\Admin;

use common\logic\Setting\SettingDefinition;
use common\logic\Setting\SettingDetails\Admin\General\AdministratorEmailAdminSetting;
use common\logic\Setting\SettingDetails\Admin\General\AdministratorPhoneNumberAdminSetting;

class AdminSettingDefinition extends SettingDefinition
{
const SECTION_GENERAL = 'general';
const SUBSECTION_GENERAL = 'general.main';

public function getSettingDetails(): array
{
return [
self::SECTION_GENERAL => [
self::SUBSECTION_GENERAL => [
new AdministratorEmailAdminSetting,
new AdministratorPhoneNumberAdminSetting,
]
],
];
}

public function getSectionLabels(): array
{
return [
self::SECTION_GENERAL => 'General',
self::SUBSECTION_GENERAL => 'General',
];
}

public function getSectionsArray(): array
{
$sectionsArray = [];

foreach($this->getSettingDetails() as $sectionName => $subsectionsArray) {
$sectionsArray[] = [
'name' => $sectionName,
'nameDisplay' => $this->getSectionLabelBySectionName($sectionName),
];
}

return $sectionsArray;
}

public function getSettingDetailsFlat(): array
{
$settingDetailsFlatArray = [];

foreach($this->getSettingDetails() as $sectionsArray) {
foreach($sectionsArray as $subsectionsArray) {
foreach($subsectionsArray as $settingDetail) {
$settingDetailsFlatArray[] = $settingDetail;
}
}
}

return $settingDetailsFlatArray;
}

public function getSectionLabelBySectionName(string $sectionName): string
{
return $this->getSectionLabels()[$sectionName];
}
}

+ 17
- 0
common/logic/Setting/SettingDetails/Admin/General/AdministratorEmailAdminSetting.php View File

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

namespace common\logic\Setting\SettingDetails\Admin\General;

use common\logic\Setting\SettingDetails\AbstractSettingDetail;

class AdministratorEmailAdminSetting extends AbstractSettingDetail
{
public function __construct()
{
$this
->setName('administratorEmail')
->setLabel("Email de l'administrateur")
->setTypeString()
->setFormTypeInput();
}
}

+ 17
- 0
common/logic/Setting/SettingDetails/Admin/General/AdministratorPhoneNumberAdminSetting.php View File

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

namespace common\logic\Setting\SettingDetails\Admin\General;

use common\logic\Setting\SettingDetails\AbstractSettingDetail;

class AdministratorPhoneNumberAdminSetting extends AbstractSettingDetail
{
public function __construct()
{
$this
->setName('administratorPhoneNumber')
->setLabel("Numéro de téléphone de l'administrateur")
->setTypeString()
->setFormTypeInput();
}
}

+ 30
- 0
common/logic/Setting/SettingDetails/Producer/ProducerSettingDefinition.php View File

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

namespace common\logic\Setting\SettingDetails\Producer;

use common\logic\Setting\SettingDefinition;

class ProducerSettingDefinition extends SettingDefinition
{
const SECTION_GENERAL = 'general';
const SUBSECTION_GENERAL = 'general.main';

public function getSettings(): array
{
return [
self::SECTION_GENERAL => [
self::SUBSECTION_GENERAL => [
// ...
]
]
];
}

public function getSectionLabels(): array
{
return [
self::SECTION_GENERAL => 'General',
self::SUBSECTION_GENERAL => 'General',
];
}
}

+ 37
- 0
common/logic/Setting/SettingImporter.php View File

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

namespace common\logic\Setting;

use common\logic\AbstractManager;
use common\logic\Setting\SettingDetails\Admin\AdminSettingDefinition;

class SettingImporter extends AbstractManager
{
protected AdminSettingDefinition $adminSettingDefinition;
protected SettingBuilder $settingBuilder;

public function loadDependencies(): void
{
$this->adminSettingDefinition = $this->loadService(AdminSettingDefinition::class);
$this->settingBuilder = $this->loadService(SettingBuilder::class);
}

public function importFromDefinitions()
{
$this->importFromAdminSettingDefinition();
$this->importFromProducerSettingDefinition();
}

public function importFromAdminSettingDefinition(): void
{
\Yii::$app->logic->setProducerContext(null);
foreach ($this->adminSettingDefinition->getSettingDetailsFlat() as $settingDetail) {
$this->settingBuilder->createSetting($settingDetail->getName());
}
}

public function importFromProducerSettingDefinition(): void
{
// /!\ bien définir le contexte producteur
}
}

+ 53
- 0
common/logic/Setting/SettingModule.php View File

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

namespace common\logic\Setting;

use common\logic\AbstractModule;
use common\logic\Setting\SettingDetails\Admin\AdminSettingDefinition;
use common\logic\Setting\SettingDetails\Producer\ProducerSettingDefinition;

class SettingModule extends AbstractModule
{
public function getServices(): array
{
return [
SettingDefinition::class,
AdminSettingDefinition::class,
ProducerSettingDefinition::class,
SettingRepository::class,
SettingBuilder::class,
SettingImporter::class,
AdminSettingBag::class
];
}

public function getDefinition(): SettingDefinition
{
return SettingDefinition::getInstance();
}

public function getAdminSettingDefinition(): AdminSettingDefinition
{
return AdminSettingDefinition::getInstance();
}

public function getProducerSettingDefinition(): ProducerSettingDefinition
{
return ProducerSettingDefinition::getInstance();
}

public function getRepository(): SettingRepository
{
return SettingRepository::getInstance();
}

public function getImporter(): SettingImporter
{
return SettingImporter::getInstance();
}

public function getAdminSettingBag(): AdminSettingBag
{
return AdminSettingBag::getInstance();
}
}

+ 50
- 0
common/logic/Setting/SettingRepository.php View File

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

namespace common\logic\Setting;

use common\logic\AbstractRepository;

class SettingRepository extends AbstractRepository
{
protected SettingRepositoryQuery $query;

public function loadDependencies(): void
{
$this->loadQuery(SettingRepositoryQuery::class);
}
public function getDefaultOptionsSearch(): array
{
return [
self::WITH => ['producer'],
self::JOIN_WITH => [],
self::ORDER_BY => '',
self::ATTRIBUTE_ID_PRODUCER => 'setting.id_producer'
];
}

public function findOneSettingByName(string $name)
{
if($this->isOutOfProducerContext()) {
return $this->findOneAdminSettingByName($name);
}
else {
return $this->findOneProducerSettingByName($name);
}
}

public function findOneAdminSettingByName(string $name)
{
return $this->createQuery()
->filterProducerIsNull()
->filterByName($name)
->findOne();
}

public function findOneProducerSettingByName(string $name)
{
return $this->createDefaultQuery()
->filterByName($name)
->findOne();
}
}

+ 27
- 0
common/logic/Setting/SettingRepositoryQuery.php View File

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

namespace common\logic\Setting;

use common\logic\AbstractRepositoryQuery;

class SettingRepositoryQuery extends AbstractRepositoryQuery
{
protected SettingDefinition $definition;

public function loadDependencies(): void
{
$this->loadDefinition(SettingDefinition::class);
}

public function filterProducerIsNull(): self
{
$this->andWhere(['id_producer' => null]);
return $this;
}

public function filterByName(string $name): self
{
$this->andWhere(['name' => $name]);
return $this;
}
}

+ 2
- 0
common/mail/orderConfirm-html.php View File

@@ -60,6 +60,8 @@ $orderModule = OrderModule::getInstance();
<?php endif; ?>
<?php if(strlen($pointSale->locality) > 0): ?> situé à <?= Html::encode($pointSale->locality) ?><?php endif ?>.</p>

<p>Retrouvez à tout moment votre commande dans l'espace de votre producteur via l'onglet <a href="<?= Yii::$app->urlManager->createAbsoluteUrl('order/history') ?>">Mes commandes</a>.</p>

<?php $payment_infos = $producerModule->getConfig('option_payment_info') ; ?>
<?php if($payment_infos && strlen($payment_infos) > 0): ?>
<p><strong>Informations de paiement :</strong><br />

+ 1
- 1
common/mail/paymentErrorProducer-text.php View File

@@ -45,7 +45,7 @@ $userModule = UserModule::getInstance();

Bonjour,

Le client <?= $userModule->getUsername() ?> vient de passer une commande pour le <?= date('d/m/Y',strtotime($distribution->date)) ?> mais le paiement par carte bancaire n'a pas abouti.
Le client <?= $userModule->getUsername($user) ?> vient de passer une commande pour le <?= date('d/m/Y',strtotime($distribution->date)) ?> mais le paiement par carte bancaire n'a pas abouti.
Il vient de recevoir un message pour régulariser le paiement par virement bancaire.

Sa commande a été enregistrée avec l'état "non payé".

+ 32
- 0
common/versions/23.11.A.php View File

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

require_once dirname(__FILE__).'/_macros.php';

version(
'13/11/2023',
[
[
"[Administration] Utilisateurs > liste : possibilité de filtrer par groupe,",
"[Administration] Utilisateurs > création : possibilité de choisir d'envoyer ou non l'email de bienvenue",
"[Administration] Utilisateurs > import : possibilité de choisir d'envoyer ou non l'email de bienvenue",
"[Administration] Documents : logo et nom/adresse du producteur sur la même ligne dans l'entête",
"[Administration] Documents : possibilité d'afficher le prix au kg dans la colonne 'Prix unitaire' des PDF",
"[Administration] Utilisateurs > formulaire crédit : possibilité de choisir si le champs 'Prévenir l'utilisateur' est coché par défaut ou non",
"[Boutique] Inscription : possibilité d'ajouter automatiquement un utilisateur à un groupe au moment de son inscription",
],
[
"[Administration] Tableau de bord : correctif lien export distribution cassé (404)",
]
],
[
[
"[Technique] Feature flag : première version"
],
[
"[Boutique] Contact : correctif spam et sender from",
]
],
$userCurrent
);

?>

+ 31
- 0
common/versions/23.11.B.php View File

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

require_once dirname(__FILE__).'/_macros.php';

version(
'20/11/2023',
[
[
"[Administration] Export : première version étiquettes avancées (formats spécifiques)",
"[Administration] Documents PDF : gestion images pied de page",
"[Administration et boutique] Points de vente : minimum de commande",
"[Boutique] Commander : envoi d'un email de confirmation lors d'une modification de commande",
"[Boutique] Produits : gestion de l'affichage en grand des images",
"[Boutique] Commander : suppression étape 'Confirmation'",
],
[
"[Administration] Communiquer > envoyer un email : correctif point de vente supprimé visible"
]
],
[
[
"[Global] Système de feature flag",
"[Technique] Paramètres : nouveau système de settings"
],
[
]
],
$userCurrent
);

?>

+ 386
- 0
common/web/js/simple-lightbox/simpleLightbox.css View File

@@ -0,0 +1,386 @@
.slbOverlay, .slbWrapOuter, .slbWrap {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
}

.slbOverlay {
overflow: hidden;
z-index: 2000;
background-color: #000;
opacity: 0.7;
-webkit-animation: slbOverlay 0.5s;
-moz-animation: slbOverlay 0.5s;
animation: slbOverlay 0.5s;
}

.slbWrapOuter {
overflow-x: hidden;
overflow-y: auto;
z-index: 2010;
}

.slbWrap {
position: absolute;
text-align: center;
}

.slbWrap:before {
content: "";
display: inline-block;
height: 100%;
vertical-align: middle;
}

.slbContentOuter {
position: relative;
display: inline-block;
vertical-align: middle;
margin: 0px auto;
padding: 0 1em;
box-sizing: border-box;
z-index: 2020;
text-align: left;
max-width: 100%;
}

.slbContentEl .slbContentOuter {
padding: 5em 1em;
}

.slbContent {
position: relative;
}

.slbContentEl .slbContent {
-webkit-animation: slbEnter 0.3s;
-moz-animation: slbEnter 0.3s;
animation: slbEnter 0.3s;
background-color: #fff;
box-shadow: 0 0.2em 1em rgba(0, 0, 0, 0.4);
}

.slbImageWrap {
-webkit-animation: slbEnter 0.3s;
-moz-animation: slbEnter 0.3s;
animation: slbEnter 0.3s;
position: relative;
}

.slbImageWrap:after {
content: "";
position: absolute;
left: 0;
right: 0;
top: 5em;
bottom: 5em;
display: block;
z-index: -1;
box-shadow: 0 0.2em 1em rgba(0, 0, 0, 0.6);
background-color: #FFF;
}

.slbDirectionNext .slbImageWrap {
-webkit-animation: slbEnterNext 0.4s;
-moz-animation: slbEnterNext 0.4s;
animation: slbEnterNext 0.4s;
}

.slbDirectionPrev .slbImageWrap {
-webkit-animation: slbEnterPrev 0.4s;
-moz-animation: slbEnterPrev 0.4s;
animation: slbEnterPrev 0.4s;
}

.slbImage {
width: auto;
max-width: 100%;
height: auto;
display: block;
line-height: 0;
box-sizing: border-box;
padding: 5em 0;
margin: 0 auto;
}

.slbCaption {
display: inline-block;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
word-wrap: normal;
font-size: 1.4em;
position: absolute;
left: 0;
right: 0;
bottom: 0;
padding: 0.71429em 0;
color: #fff;
color: rgba(255, 255, 255, 0.7);
text-align: center;
}

.slbCloseBtn, .slbArrow {
margin: 0;
padding: 0;
border: 0;
cursor: pointer;
background: none;
}

.slbCloseBtn::-moz-focus-inner, .slbArrow::-moz-focus-inner {
padding: 0;
border: 0;
}

.slbCloseBtn:hover, .slbArrow:hover {
opacity: 0.5;
}

.slbCloseBtn:active, .slbArrow:active {
opacity: 0.8;
}

.slbCloseBtn {
-webkit-animation: slbEnter 0.3s;
-moz-animation: slbEnter 0.3s;
animation: slbEnter 0.3s;
font-size: 3em;
width: 1.66667em;
height: 1.66667em;
line-height: 1.66667em;
position: absolute;
right: -0.33333em;
top: 0;
color: #fff;
color: rgba(255, 255, 255, 0.7);
text-align: center;
}

.slbLoading .slbCloseBtn {
display: none;
}

.slbLoadingText {
font-size: 1.4em;
color: #fff;
color: rgba(255, 255, 255, 0.9);
}

.slbArrows {
position: fixed;
top: 50%;
left: 0;
right: 0;
}

.slbLoading .slbArrows {
display: none;
}

.slbArrow {
position: absolute;
top: 50%;
margin-top: -5em;
width: 5em;
height: 10em;
opacity: 0.7;
text-indent: -999em;
overflow: hidden;
}

.slbArrow:before {
content: "";
position: absolute;
top: 50%;
left: 50%;
margin: -0.8em 0 0 -0.8em;
border: 0.8em solid transparent;
}

.slbArrow.next {
right: 0;
}

.slbArrow.next:before {
border-left-color: #fff;
}

.slbArrow.prev {
left: 0;
}

.slbArrow.prev:before {
border-right-color: #fff;
}

.slbIframeCont {
width: 80em;
height: 0;
overflow: hidden;
padding-top: 56.25%;
margin: 5em 0;
}

.slbIframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
box-shadow: 0 0.2em 1em rgba(0, 0, 0, 0.6);
background: #000;
}

@-webkit-keyframes slbOverlay {
from {
opacity: 0;
}
to {
opacity: 0.7;
}
}

@-moz-keyframes slbOverlay {
from {
opacity: 0;
}
to {
opacity: 0.7;
}
}

@keyframes slbOverlay {
from {
opacity: 0;
}
to {
opacity: 0.7;
}
}

@-webkit-keyframes slbEnter {
from {
opacity: 0;
-webkit-transform: translate3d(0, -1em, 0);
}
to {
opacity: 1;
-webkit-transform: translate3d(0, 0, 0);
}
}

@-moz-keyframes slbEnter {
from {
opacity: 0;
-moz-transform: translate3d(0, -1em, 0);
}
to {
opacity: 1;
-moz-transform: translate3d(0, 0, 0);
}
}

@keyframes slbEnter {
from {
opacity: 0;
-webkit-transform: translate3d(0, -1em, 0);
-moz-transform: translate3d(0, -1em, 0);
-ms-transform: translate3d(0, -1em, 0);
-o-transform: translate3d(0, -1em, 0);
transform: translate3d(0, -1em, 0);
}
to {
opacity: 1;
-webkit-transform: translate3d(0, 0, 0);
-moz-transform: translate3d(0, 0, 0);
-ms-transform: translate3d(0, 0, 0);
-o-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
}

@-webkit-keyframes slbEnterNext {
from {
opacity: 0;
-webkit-transform: translate3d(4em, 0, 0);
}
to {
opacity: 1;
-webkit-transform: translate3d(0, 0, 0);
}
}

@-moz-keyframes slbEnterNext {
from {
opacity: 0;
-moz-transform: translate3d(4em, 0, 0);
}
to {
opacity: 1;
-moz-transform: translate3d(0, 0, 0);
}
}

@keyframes slbEnterNext {
from {
opacity: 0;
-webkit-transform: translate3d(4em, 0, 0);
-moz-transform: translate3d(4em, 0, 0);
-ms-transform: translate3d(4em, 0, 0);
-o-transform: translate3d(4em, 0, 0);
transform: translate3d(4em, 0, 0);
}
to {
opacity: 1;
-webkit-transform: translate3d(0, 0, 0);
-moz-transform: translate3d(0, 0, 0);
-ms-transform: translate3d(0, 0, 0);
-o-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
}

@-webkit-keyframes slbEnterPrev {
from {
opacity: 0;
-webkit-transform: translate3d(-4em, 0, 0);
}
to {
opacity: 1;
-webkit-transform: translate3d(0, 0, 0);
}
}

@-moz-keyframes slbEnterPrev {
from {
opacity: 0;
-moz-transform: translate3d(-4em, 0, 0);
}
to {
opacity: 1;
-moz-transform: translate3d(0, 0, 0);
}
}

@keyframes slbEnterPrev {
from {
opacity: 0;
-webkit-transform: translate3d(-4em, 0, 0);
-moz-transform: translate3d(-4em, 0, 0);
-ms-transform: translate3d(-4em, 0, 0);
-o-transform: translate3d(-4em, 0, 0);
transform: translate3d(-4em, 0, 0);
}
to {
opacity: 1;
-webkit-transform: translate3d(0, 0, 0);
-moz-transform: translate3d(0, 0, 0);
-ms-transform: translate3d(0, 0, 0);
-o-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
}

+ 532
- 0
common/web/js/simple-lightbox/simpleLightbox.js View File

@@ -0,0 +1,532 @@
(function(root, factory) {

if (typeof define === 'function' && define.amd) {
define([], factory);
} else if (typeof module === 'object' && module.exports) {
module.exports = factory();
} else {
root.SimpleLightbox = factory();
}

}(this, function() {

function assign(target) {

for (var i = 1; i < arguments.length; i++) {

var obj = arguments[i];

if (obj) {
for (var key in obj) {
obj.hasOwnProperty(key) && (target[key] = obj[key]);
}
}

}

return target;

}

function addClass(element, className) {

if (element && className) {
element.className += ' ' + className;
}

}

function removeClass(element, className) {

if (element && className) {
element.className = element.className.replace(
new RegExp('(\\s|^)' + className + '(\\s|$)'), ' '
).trim();
}

}

function parseHtml(html) {

var div = document.createElement('div');
div.innerHTML = html.trim();

return div.childNodes[0];

}

function matches(el, selector) {

return (el.matches || el.matchesSelector || el.msMatchesSelector).call(el, selector);

}

function getWindowHeight() {

return 'innerHeight' in window
? window.innerHeight
: document.documentElement.offsetHeight;

}

function SimpleLightbox(options) {

this.init.apply(this, arguments);

}

SimpleLightbox.defaults = {

// add custom classes to lightbox elements
elementClass: '',
elementLoadingClass: 'slbLoading',
htmlClass: 'slbActive',
closeBtnClass: '',
nextBtnClass: '',
prevBtnClass: '',
loadingTextClass: '',

// customize / localize controls captions
closeBtnCaption: 'Close',
nextBtnCaption: 'Next',
prevBtnCaption: 'Previous',
loadingCaption: 'Loading...',

bindToItems: true, // set click event handler to trigger lightbox on provided $items
closeOnOverlayClick: true,
closeOnEscapeKey: true,
nextOnImageClick: true,
showCaptions: true,

captionAttribute: 'title', // choose data source for library to glean image caption from
urlAttribute: 'href', // where to expect large image

startAt: 0, // start gallery at custom index
loadingTimeout: 100, // time after loading element will appear

appendTarget: 'body', // append elsewhere if needed

beforeSetContent: null, // convenient hooks for extending library behavoiur
beforeClose: null,
afterClose: null,
beforeDestroy: null,
afterDestroy: null,

videoRegex: new RegExp(/youtube.com|vimeo.com/) // regex which tests load url for iframe content

};

assign(SimpleLightbox.prototype, {

init: function(options) {

options = this.options = assign({}, SimpleLightbox.defaults, options);

var self = this;
var elements;

if (options.$items) {
elements = options.$items.get();
}

if (options.elements) {
elements = [].slice.call(
typeof options.elements === 'string'
? document.querySelectorAll(options.elements)
: options.elements
);
}

this.eventRegistry = {lightbox: [], thumbnails: []};
this.items = [];
this.captions = [];

if (elements) {

elements.forEach(function(element, index) {

self.items.push(element.getAttribute(options.urlAttribute));
self.captions.push(element.getAttribute(options.captionAttribute));

if (options.bindToItems) {

self.addEvent(element, 'click', function(e) {

e.preventDefault();
self.showPosition(index);

}, 'thumbnails');

}

});

}

if (options.items) {
this.items = options.items;
}

if (options.captions) {
this.captions = options.captions;
}

},

addEvent: function(element, eventName, callback, scope) {

this.eventRegistry[scope || 'lightbox'].push({
element: element,
eventName: eventName,
callback: callback
});

element.addEventListener(eventName, callback);

return this;

},

removeEvents: function(scope) {

this.eventRegistry[scope].forEach(function(item) {
item.element.removeEventListener(item.eventName, item.callback);
});

this.eventRegistry[scope] = [];

return this;

},

next: function() {

return this.showPosition(this.currentPosition + 1);

},

prev: function() {

return this.showPosition(this.currentPosition - 1);

},

normalizePosition: function(position) {

if (position >= this.items.length) {
position = 0;
} else if (position < 0) {
position = this.items.length - 1;
}

return position;

},

showPosition: function(position) {

var newPosition = this.normalizePosition(position);

if (typeof this.currentPosition !== 'undefined') {
this.direction = newPosition > this.currentPosition ? 'next' : 'prev';
}

this.currentPosition = newPosition;

return this.setupLightboxHtml()
.prepareItem(this.currentPosition, this.setContent)
.show();

},

loading: function(on) {

var self = this;
var options = this.options;

if (on) {

this.loadingTimeout = setTimeout(function() {

addClass(self.$el, options.elementLoadingClass);

self.$content.innerHTML =
'<p class="slbLoadingText ' + options.loadingTextClass + '">' +
options.loadingCaption +
'</p>';
self.show();

}, options.loadingTimeout);

} else {

removeClass(this.$el, options.elementLoadingClass);
clearTimeout(this.loadingTimeout);

}

},

prepareItem: function(position, callback) {

var self = this;
var url = this.items[position];

this.loading(true);

if (this.options.videoRegex.test(url)) {

callback.call(self, parseHtml(
'<div class="slbIframeCont"><iframe class="slbIframe" frameborder="0" allowfullscreen src="' + url + '"></iframe></div>')
);

} else {

var $imageCont = parseHtml(
'<div class="slbImageWrap"><img class="slbImage" src="' + url + '" /></div>'
);

this.$currentImage = $imageCont.querySelector('.slbImage');

if (this.options.showCaptions && this.captions[position]) {
$imageCont.appendChild(parseHtml(
'<div class="slbCaption">' + this.captions[position] + '</div>')
);
}

this.loadImage(url, function() {

self.setImageDimensions();

callback.call(self, $imageCont);

self.loadImage(self.items[self.normalizePosition(self.currentPosition + 1)]);

});

}

return this;

},

loadImage: function(url, callback) {

if (!this.options.videoRegex.test(url)) {

var image = new Image();
callback && (image.onload = callback);
image.src = url;

}

},

setupLightboxHtml: function() {

var o = this.options;

if (!this.$el) {

this.$el = parseHtml(
'<div class="slbElement ' + o.elementClass + '">' +
'<div class="slbOverlay"></div>' +
'<div class="slbWrapOuter">' +
'<div class="slbWrap">' +
'<div class="slbContentOuter">' +
'<div class="slbContent"></div>' +
'<button type="button" title="' + o.closeBtnCaption + '" class="slbCloseBtn ' + o.closeBtnClass + '">×</button>' +
(this.items.length > 1
? '<div class="slbArrows">' +
'<button type="button" title="' + o.prevBtnCaption + '" class="prev slbArrow' + o.prevBtnClass + '">' + o.prevBtnCaption + '</button>' +
'<button type="button" title="' + o.nextBtnCaption + '" class="next slbArrow' + o.nextBtnClass + '">' + o.nextBtnCaption + '</button>' +
'</div>'
: ''
) +
'</div>' +
'</div>' +
'</div>' +
'</div>'
);

this.$content = this.$el.querySelector('.slbContent');

}

this.$content.innerHTML = '';

return this;

},

show: function() {

if (!this.modalInDom) {

document.querySelector(this.options.appendTarget).appendChild(this.$el);
addClass(document.documentElement, this.options.htmlClass);
this.setupLightboxEvents();
this.modalInDom = true;

}

return this;

},

setContent: function(content) {

var $content = typeof content === 'string'
? parseHtml(content)
: content
;

this.loading(false);

this.setupLightboxHtml();

removeClass(this.$content, 'slbDirectionNext');
removeClass(this.$content, 'slbDirectionPrev');

if (this.direction) {
addClass(this.$content, this.direction === 'next'
? 'slbDirectionNext'
: 'slbDirectionPrev'
);
}

if (this.options.beforeSetContent) {
this.options.beforeSetContent($content, this);
}

this.$content.appendChild($content);

return this;

},

setImageDimensions: function() {

if (this.$currentImage) {
this.$currentImage.style.maxHeight = getWindowHeight() + 'px';
}

},

setupLightboxEvents: function() {

var self = this;

if (this.eventRegistry.lightbox.length) {
return this;
}

this.addEvent(this.$el, 'click', function(e) {

var $target = e.target;

if (matches($target, '.slbCloseBtn') || (self.options.closeOnOverlayClick && matches($target, '.slbWrap'))) {

self.close();

} else if (matches($target, '.slbArrow')) {

matches($target, '.next') ? self.next() : self.prev();

} else if (self.options.nextOnImageClick && self.items.length > 1 && matches($target, '.slbImage')) {

self.next();

}

}).addEvent(document, 'keyup', function(e) {

self.options.closeOnEscapeKey && e.keyCode === 27 && self.close();

if (self.items.length > 1) {
(e.keyCode === 39 || e.keyCode === 68) && self.next();
(e.keyCode === 37 || e.keyCode === 65) && self.prev();
}

}).addEvent(window, 'resize', function() {

self.setImageDimensions();

});

return this;

},

close: function() {

if (this.modalInDom) {

this.runHook('beforeClose');
this.removeEvents('lightbox');
this.$el && this.$el.parentNode.removeChild(this.$el);
removeClass(document.documentElement, this.options.htmlClass);
this.modalInDom = false;
this.runHook('afterClose');

}

this.direction = undefined;
this.currentPosition = this.options.startAt;

},

destroy: function() {

this.close();
this.runHook('beforeDestroy');
this.removeEvents('thumbnails');
this.runHook('afterDestroy');

},

runHook: function(name) {

this.options[name] && this.options[name](this);

}

});

SimpleLightbox.open = function(options) {

var instance = new SimpleLightbox(options);

return options.content
? instance.setContent(options.content).show()
: instance.showPosition(instance.options.startAt);

};

SimpleLightbox.registerAsJqueryPlugin = function($) {

$.fn.simpleLightbox = function(options) {

var lightboxInstance;
var $items = this;

return this.each(function() {
if (!$.data(this, 'simpleLightbox')) {
lightboxInstance = lightboxInstance || new SimpleLightbox($.extend({}, options, {$items: $items}));
$.data(this, 'simpleLightbox', lightboxInstance);
}
});

};

$.SimpleLightbox = SimpleLightbox;

};

if (typeof window !== 'undefined' && window.jQuery) {
SimpleLightbox.registerAsJqueryPlugin(window.jQuery);
}

return SimpleLightbox;

}));

+ 1
- 0
common/web/js/simple-lightbox/simpleLightbox.min.css
File diff suppressed because it is too large
View File


+ 1
- 0
common/web/js/simple-lightbox/simpleLightbox.min.js
File diff suppressed because it is too large
View File


+ 2
- 1
composer.json View File

@@ -37,7 +37,8 @@
"weluse/yii2-mailjet": "^0.2.0",
"ext-json": "*",
"ext-curl": "*",
"yiisoft/yii2-twig": "^2.4"
"yiisoft/yii2-twig": "^2.4",
"letyii/yii2-tinymce": "dev-master"
},
"require-dev": {
"yiisoft/yii2-codeception": "*",

+ 104
- 2
composer.lock View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "a8cbf9ba6bfa2f0b47fba2344732ac95",
"content-hash": "95e88fd70427210dc0d1edd79d17cce1",
"packages": [
{
"name": "2amigos/yii2-chartjs-widget",
@@ -1190,6 +1190,48 @@
],
"time": "2022-09-19T18:31:07+00:00"
},
{
"name": "letyii/yii2-tinymce",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/letyii/yii2-tinymce.git",
"reference": "861873f30d9e16f76239ae827f2848879f66f78f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/letyii/yii2-tinymce/zipball/861873f30d9e16f76239ae827f2848879f66f78f",
"reference": "861873f30d9e16f76239ae827f2848879f66f78f",
"shasum": ""
},
"require": {
"php": ">=5.4",
"tinymce/tinymce": ">=4",
"yiisoft/yii2": "*"
},
"default-branch": true,
"type": "library",
"autoload": {
"psr-4": {
"letyii\\tinymce\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "TinyMce for yii2 textarea",
"homepage": "https://github.com/letyii/yii2-tinymce",
"keywords": [
"tinymce",
"yii2"
],
"support": {
"issues": "https://github.com/letyii/yii2-tinymce/issues",
"source": "https://github.com/letyii/yii2-tinymce/tree/master"
},
"time": "2015-07-08T08:11:14+00:00"
},
{
"name": "linslin/yii2-curl",
"version": "1.5.0",
@@ -2546,6 +2588,65 @@
],
"time": "2022-11-03T14:55:06+00:00"
},
{
"name": "tinymce/tinymce",
"version": "6.7.2",
"source": {
"type": "git",
"url": "https://github.com/tinymce/tinymce-dist.git",
"reference": "a4c139bde17a4e8b2e84d225020cd5acc24a728a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/tinymce/tinymce-dist/zipball/a4c139bde17a4e8b2e84d225020cd5acc24a728a",
"reference": "a4c139bde17a4e8b2e84d225020cd5acc24a728a",
"shasum": ""
},
"type": "component",
"extra": {
"component": {
"scripts": [
"tinymce.js",
"plugins/*/plugin.js",
"themes/*/theme.js",
"models/*/model.js",
"icons/*/icons.js"
],
"files": [
"tinymce.min.js",
"plugins/*/plugin.min.js",
"themes/*/theme.min.js",
"models/*/model.min.js",
"skins/**",
"icons/*/icons.min.js"
]
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT-only"
],
"description": "Web based JavaScript HTML WYSIWYG editor control.",
"homepage": "https://www.tiny.cloud/",
"keywords": [
"contenteditable",
"editing",
"html",
"javascript",
"rich editor",
"rich text",
"rich text editor",
"richtext",
"rte",
"text",
"tinymce",
"wysiwyg"
],
"support": {
"source": "https://github.com/tinymce/tinymce-dist/tree/6.7.2"
},
"time": "2023-10-25T06:42:18+00:00"
},
{
"name": "twig/twig",
"version": "v3.7.1",
@@ -6669,7 +6770,8 @@
"stability-flags": {
"kartik-v/yii2-mpdf": 20,
"c006/yii2-paypal-ipn": 20,
"yurkinx/yii2-image": 20
"yurkinx/yii2-image": 20,
"letyii/yii2-tinymce": 20
},
"prefer-stable": false,
"prefer-lowest": false,

+ 2
- 1
console/commands/ImportFeaturesController.php View File

@@ -2,11 +2,12 @@

namespace console\commands;

use common\logic\Feature\Feature\Module\FeatureModule;
use common\logic\Feature\Feature\FeatureModule;
use yii\console\Controller;

class ImportFeaturesController extends Controller
{
// ./yii import-features/index
public function actionIndex()
{
$featureModule = FeatureModule::getInstance();

+ 18
- 0
console/commands/SettingController.php View File

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

namespace console\commands;

use common\logic\Setting\SettingModule;
use yii\console\Controller;

class SettingController extends Controller
{
// ./yii setting/import
public function actionImport()
{
$settingModule = SettingModule::getInstance();
$settingModule->getImporter()->importFromDefinitions();
}
}

?>

+ 25
- 0
console/migrations/m231113_073008_add_column_feature_position.php View File

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

use yii\db\Migration;

/**
* Class m231113_073008_add_column_feature_position
*/
class m231113_073008_add_column_feature_position extends Migration
{
/**
* {@inheritdoc}
*/
public function safeUp()
{
$this->addColumn('feature', 'position', $this->integer()->notNull()->defaultValue(0));
}

/**
* {@inheritdoc}
*/
public function safeDown()
{
$this->dropColumn('feature', 'position');
}
}

+ 26
- 0
console/migrations/m231113_084553_add_column_point_sale_minimum_order_amount.php View File

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

use yii\db\Migration;
use yii\db\Schema;

/**
* Class m231113_084553_add_column_point_sale_minimum_order_amount
*/
class m231113_084553_add_column_point_sale_minimum_order_amount extends Migration
{
/**
* {@inheritdoc}
*/
public function safeUp()
{
$this->addColumn('point_sale', 'minimum_order_amount', Schema::TYPE_FLOAT);
}

/**
* {@inheritdoc}
*/
public function safeDown()
{
$this->dropColumn('point_sale', 'minimum_order_amount');
}
}

+ 26
- 0
console/migrations/m231113_131131_add_column_producer_export_shopping_cart_labels_format.php View File

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

use yii\db\Migration;
use yii\db\Schema;

/**
* Class m231113_131131_add_column_producer_export_shopping_cart_labels_format
*/
class m231113_131131_add_column_producer_export_shopping_cart_labels_format extends Migration
{
/**
* {@inheritdoc}
*/
public function safeUp()
{
$this->addColumn('producer', 'export_shopping_cart_labels_format', Schema::TYPE_STRING);
}

/**
* {@inheritdoc}
*/
public function safeDown()
{
$this->dropColumn('producer', 'export_shopping_cart_labels_format');
}
}

+ 37
- 0
console/migrations/m231114_085352_create_table_setting.php View File

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

use yii\db\Migration;
use yii\db\Schema;

/**
* Class m231114_085352_create_table_setting
*/
class m231114_085352_create_table_setting extends Migration
{
/**
* {@inheritdoc}
*/
public function safeUp()
{
$this->createTable('setting', [
'id' => 'pk',
'name' => Schema::TYPE_STRING.' NOT NULL',
'id_producer' => Schema::TYPE_INTEGER,
'string' => Schema::TYPE_STRING,
'text' => Schema::TYPE_TEXT,
'date' => Schema::TYPE_DATE,
'integer' => Schema::TYPE_INTEGER,
'float' => Schema::TYPE_FLOAT,
'double' => Schema::TYPE_DOUBLE,
'boolean' => Schema::TYPE_BOOLEAN,
]);
}

/**
* {@inheritdoc}
*/
public function safeDown()
{
$this->dropTable('setting');
}
}

+ 26
- 0
console/migrations/m231115_083420_add_column_producer_document_image_bottom.php View File

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

use yii\db\Migration;
use yii\db\Schema;

/**
* Class m231115_083420_add_column_producer_document_image_bottom
*/
class m231115_083420_add_column_producer_document_image_bottom extends Migration
{
/**
* {@inheritdoc}
*/
public function safeUp()
{
$this->addColumn('producer', 'document_image_bottom', Schema::TYPE_STRING);
}

/**
* {@inheritdoc}
*/
public function safeDown()
{
$this->dropColumn('producer', 'document_image_bottom');
}
}

+ 4
- 1
frontend/controllers/SiteController.php View File

@@ -138,9 +138,12 @@ class SiteController extends FrontendController

public function actionService()
{
$paidFeaturesArray = $this->getFeatureModule()->getRepository()->findPaidFeatures();

return $this->render('service', [
'producerDemoAccount' => $this->getProducerModule()->findOneProducerDemoAccount(),
'dataProviderPrices' => $this->getDataProviderPrices()
'dataProviderPrices' => $this->getDataProviderPrices(),
'paidFeaturesArray' => $paidFeaturesArray
]);
}


+ 7
- 6
frontend/views/site/_prices_producer.php View File

@@ -36,7 +36,9 @@
* termes.
*/

use common\helpers\Price;
use yii\grid\GridView;
use yii\helpers\Html;

?>

@@ -89,16 +91,15 @@ GridView::widget([
</tr>
</thead>
<tbody>
<?php foreach($paidFeaturesArray as $paidFeature): ?>
<tr>
<td>
<strong>Paiement en ligne</strong><br>
<p>Le paiement en ligne permet à vos clients d'alimenter leur crédit (compte prépayé en ligne) par carte bancaire.<br>
Le logiciel fonctionne avec la plateforme <a href="https://stripe.com/fr">Stripe</a> pour accepter les paiements.<br>
<a href="https://stripe.com/fr/pricing">Voir les tarifs Stripe</a>
</p>
<div><strong><?= Html::encode($paidFeature->name) ?></strong></div>
<div><?= $paidFeature->description ?></div>
</td>
<td>120 €</td>
<td><?= Price::format($paidFeature->price, 0) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>


+ 4
- 1
frontend/views/site/service.php View File

@@ -140,7 +140,10 @@ $this->setIcon('console');
</h2>
</div>
<div class="panel-body">
<?= $this->render('_prices_producer', ['dataProviderPrices' => $dataProviderPrices]); ?>
<?= $this->render('_prices_producer', [
'dataProviderPrices' => $dataProviderPrices,
'paidFeaturesArray' => $paidFeaturesArray
]); ?>
</div>
</div>


+ 131
- 125
producer/controllers/CreditController.php View File

@@ -40,6 +40,7 @@ namespace producer\controllers;

use common\helpers\GlobalParam;
use common\helpers\MeanPayment;
use common\logic\Feature\Feature\Feature;
use common\logic\Payment\Model\Payment;
use producer\models\CreditForm;
use yii\filters\VerbFilter;
@@ -108,12 +109,14 @@ class CreditController extends ProducerBaseController

public function actionAdd()
{
$featureChecker = $this->getFeatureModule()->getChecker();
$producer = $this->getProducerCurrent();
if (\Yii::$app->user->isGuest) {
return $this->redirect($this->getUrlManagerFrontend()->createAbsoluteUrl(['site/producer', 'id' => $producer->id]));
}

if ($producer->online_payment || $producer->option_stripe_mode_test) {
if ($featureChecker->isEnabled(Feature::ALIAS_ONLINE_PAYMENT)
&& ($producer->online_payment || $producer->option_stripe_mode_test)) {

$creditForm = new CreditForm;

@@ -132,7 +135,7 @@ class CreditController extends ProducerBaseController
'product_data' => [
'name' => 'Alimentation crédit',
],
'unit_amount' => (float) $creditForm->amount * 100,
'unit_amount' => (float)$creditForm->amount * 100,
],
'quantity' => 1,
]
@@ -180,153 +183,156 @@ class CreditController extends ProducerBaseController
$paymentManager = $this->getPaymentModule();
$producerModule = $this->getProducerModule();
$userModule = $this->getUserModule();
$producer = $this->getProducerCurrent();
$contactProducer = $producerModule->getMainContact($producer);

$payload = @file_get_contents('php://input');
$sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE'];

try {
$event = \Stripe\Webhook::constructEvent(
$payload,
$sig_header,
$producerModule->getPrivateKeyEndpointStripe($producer)
);
} catch (\UnexpectedValueException $e) {
// Invalid payload
http_response_code(400);
exit();
} catch (\Stripe\Exception\SignatureVerificationException $e) {
// Invalid signature
http_response_code(400);
exit();
}
$featureChecker = $this->getFeatureModule()->getChecker();

$paymentIntent = $event->data->object;
$paymentIntentMetadata = $paymentIntent->metadata;
$amount = $paymentIntent->amount / 100;
$idUser = $paymentIntentMetadata->user_id;
if ($featureChecker->isEnabled(Feature::ALIAS_ONLINE_PAYMENT)) {
$producer = $this->getProducerCurrent();
$contactProducer = $producerModule->getMainContact($producer);

if($idUser) {
$user = $userModule->findOneUserById($idUser);
$payload = @file_get_contents('php://input');
$sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE'];

if (isset($paymentIntentMetadata->order_id)) {
$order = $this->getOrderModule()->findOneOrderById($paymentIntentMetadata->order_id);
$orderModule->initOrder($order);
$pointSale = $this->getPointSaleModule()->findOnePointSaleById($order->id_point_sale);
$distribution = $this-> getDistributionModule()->findOneDistributionById($order->id_distribution);
try {
$event = \Stripe\Webhook::constructEvent(
$payload,
$sig_header,
$producerModule->getPrivateKeyEndpointStripe($producer)
);
} catch (\UnexpectedValueException $e) {
// Invalid payload
http_response_code(400);
exit();
} catch (\Stripe\Exception\SignatureVerificationException $e) {
// Invalid signature
http_response_code(400);
exit();
}

// Handle the event
switch ($event->type) {
case 'charge.succeeded':
$paymentIntent = $event->data->object;
$paymentIntentMetadata = $paymentIntent->metadata;
$amount = $paymentIntent->amount / 100;
$idUser = $paymentIntentMetadata->user_id;

$paymentExist = Payment::searchOne([
'id_user' => $idUser,
'amount' => $amount,
], [
'conditions' => [
'date > DATE_SUB(NOW(), INTERVAL 1 MINUTE)'
]
]);
if ($idUser) {
$user = $userModule->findOneUserById($idUser);

if (!$paymentExist) {
if (isset($paymentIntentMetadata->order_id)) {
$order = $this->getOrderModule()->findOneOrderById($paymentIntentMetadata->order_id);
$orderModule->initOrder($order);
$pointSale = $this->getPointSaleModule()->findOnePointSaleById($order->id_point_sale);
$distribution = $this->getDistributionModule()->findOneDistributionById($order->id_distribution);
}

$paymentManager->creditUser($user, $amount, MeanPayment::CREDIT_CARD, $user);
// Handle the event
switch ($event->type) {
case 'charge.succeeded':

if (isset($order) && $order) {
$paymentExist = Payment::searchOne([
'id_user' => $idUser,
'amount' => $amount,
], [
'conditions' => [
'date > DATE_SUB(NOW(), INTERVAL 1 MINUTE)'
]
]);

if (!$paymentExist) {

$paymentManager->creditUser($user, $amount, MeanPayment::CREDIT_CARD, $user);

if (isset($order) && $order) {

$paymentManager->payOrder($order, MeanPayment::CREDIT_CARD, $user, true);


// client : envoi d'un email de confirmation de paiement
/*\Yii::$app->mailerService->sendFromProducer(
'Confirmation de commande',
'paymentOrderConfirm',
[
'amount' => $amount,
'user' => $user,
'producer' => $producer,
],
$user->email,
$producer
);*/

// producteur : mail de confirmation
\Yii::$app->mailerService->sendFromSite(
'Confirmation de commande',
'orderConfirmProducer',
[
'order' => $order,
'pointSale' => $pointSale,
'distribution' => $distribution,
'user' => $user,
'producer' => $producer
],
$contactProducer->email
);
} else {
$userProducer = $this->getUserProducerModule()->findOneUserProducer($user);

\Yii::$app->mailerService->sendFromProducer(
'Alimentation de votre crédit',
'creditConfirm',
[
'user' => $user,
'userProducer' => $userProducer,
'producer' => $producer,
'amount' => $amount,
],
$user->email,
$producer
);
}
}

$paymentManager->payOrder($order, MeanPayment::CREDIT_CARD, $user, true);
break;

case 'charge.failed':

// client : envoi d'un email de confirmation de paiement
/*\Yii::$app->mailerService->sendFromProducer(
'Confirmation de commande',
'paymentOrderConfirm',
[
'amount' => $amount,
'user' => $user,
'producer' => $producer,
],
$user->email,
$producer
);*/
// client
\Yii::$app->mailerService->sendFromProducer(
'Erreur de paiement',
'paymentError',
[
'amount' => $amount,
'user' => $user,
'producer' => $producer,
],
$user->email,
$producer
);

// producteur : mail de confirmation
// producteur
if (isset($order) && $order) {
\Yii::$app->mailerService->sendFromSite(
'Confirmation de commande',
'orderConfirmProducer',
[
'order' => $order,
'pointSale' => $pointSale,
'distribution' => $distribution,
'user' => $user,
'producer' => $producer
],
$contactProducer->email
);
} else {
$userProducer = $this->getUserProducerModule()->findOneUserProducer($user);

\Yii::$app->mailerService->sendFromProducer(
'Alimentation de votre crédit',
'creditConfirm',
'Erreur de paiement',
'paymentErrorProducer',
[
'amount' => $amount,
'user' => $user,
'userProducer' => $userProducer,
'producer' => $producer,
'amount' => $amount,
'order' => $order,
'distribution' => $distribution
],
$user->email,
$producer
$contactProducer->email,
);
}
}

break;

case 'charge.failed':

// client
\Yii::$app->mailerService->sendFromProducer(
'Erreur de paiement',
'paymentError',
[
'amount' => $amount,
'user' => $user,
'producer' => $producer,
],
$user->email,
$producer
);

// producteur
if (isset($order) && $order) {
\Yii::$app->mailerService->sendFromSite(
'Erreur de paiement',
'paymentErrorProducer',
[
'amount' => $amount,
'user' => $user,
'producer' => $producer,
'order' => $order,
'distribution' => $distribution
],
$contactProducer->email,
);
}
break;

break;
// handle other event types
default:
echo 'Received unknown event type ' . $event->type;
}

// handle other event types
default:
echo 'Received unknown event type ' . $event->type;
http_response_code(200);
} else {
http_response_code(200);
}

http_response_code(200);
}
else {
http_response_code(200);
}

die();

+ 33
- 35
producer/controllers/OrderController.php View File

@@ -50,7 +50,6 @@ use common\logic\Order\ProductOrder\Model\ProductOrder;
use common\logic\PointSale\PointSale\Model\PointSale;
use common\logic\Producer\Producer\Model\Producer;
use common\logic\Product\Product\Model\Product;
use common\logic\User\CreditHistory\Model\CreditHistory;
use common\logic\User\User\Model\User;
use DateTime;
use yii\base\UserException;
@@ -476,44 +475,42 @@ class OrderController extends ProducerBaseController
/*
* Envoi email de confirmation
*/
if ($isNewOrder) {

$emailSubject = 'Confirmation de commande';
$emailContentParams = [
'order' => $order,
'pointSale' => $pointSale,
'distribution' => $distribution,
'user' => $user,
'producer' => $producer
];

// au client
if ($producerModule->getConfig('option_email_confirm')) {
\Yii::$app->mailerService->sendFromProducer(
$emailSubject,
'orderConfirm',
$emailContentParams,
$user->email,
$producer
);
$emailSubject = 'Confirmation de commande';
$emailContentParams = [
'order' => $order,
'pointSale' => $pointSale,
'distribution' => $distribution,
'user' => $user,
'producer' => $producer
];

}
// au client
if ($producerModule->getConfig('option_email_confirm')) {
\Yii::$app->mailerService->sendFromProducer(
$emailSubject,
'orderConfirm',
$emailContentParams,
$user->email,
$producer
);

// au producteur
$contactProducer = $producerModule->getMainContact($producer);
if ($producerModule->getConfig('option_email_confirm_producer') && $contactProducer && strlen(
$contactProducer->email
)) {

\Yii::$app->mailerService->sendFromSite(
$emailSubject,
'orderConfirmProducer',
$emailContentParams,
$contactProducer->email
);
}
}

// au producteur
$contactProducer = $producerModule->getMainContact($producer);
if ($producerModule->getConfig('option_email_confirm_producer') && $contactProducer && strlen(
$contactProducer->email
)) {

\Yii::$app->mailerService->sendFromSite(
$emailSubject,
'orderConfirmProducer',
$emailContentParams,
$contactProducer->email
);
}


$order = $orderModule->findOneOrderById($order->id);
$orderModule->initOrder($order);
$orderModule->updateOrderTillerSynchronization($order);
@@ -935,6 +932,7 @@ class OrderController extends ProducerBaseController
$product['photo'] = '';
}
else {
$product['photo_big'] = Image::getThumbnailBig($product['photo']);
$product['photo'] = Image::getThumbnailSmall($product['photo']);
}


+ 2
- 2
producer/controllers/SiteController.php View File

@@ -40,7 +40,7 @@ namespace producer\controllers;

use common\forms\ContactForm;
use common\helpers\GlobalParam;
use common\logic\Feature\Feature\Model\Feature;
use common\logic\Feature\Feature\Feature;
use common\logic\Product\Product\Model\Product;
use yii\data\ActiveDataProvider;
use yii\helpers\Html;
@@ -180,7 +180,7 @@ class SiteController extends ProducerBaseController
public function actionContact()
{
$featureModule = $this->getFeatureModule();
if($featureModule->getManager()->isDisabled(Feature::ALIAS_CONTACT)) {
if($featureModule->getChecker()->isDisabled(Feature::ALIAS_CONTACT)) {
return $this->redirect(['site/index']);
}


+ 4
- 1
producer/views/credit/history.php View File

@@ -36,15 +36,18 @@
* termes.
*/

use common\logic\Feature\Feature\Feature;
use common\logic\Feature\Feature\FeatureModule;
use common\logic\Payment\Module\PaymentModule;
use yii\grid\GridView;

$paymentManager = PaymentModule::getInstance();
$featureChecker = FeatureModule::getInstance()->getChecker();
$producer = $this->context->getProducerCurrent();
$this->setTitle('Crédit : <span id="credit-user">' . number_format($creditUser, 2) . ' €</span>');
$this->setPageTitle('Crédit');

if ($this->context->getProducerCurrent()->online_payment) {
if ($featureChecker->isEnabled(Feature::ALIAS_ONLINE_PAYMENT) && $this->context->getProducerCurrent()->online_payment) {
$this->addButton(
[
'label' => '<span class="glyphicon glyphicon-credit-card"></span> Créditer mon compte',

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

@@ -36,8 +36,8 @@
* termes.
*/

use common\logic\Feature\Feature\Model\Feature;
use common\logic\Feature\Feature\Module\FeatureModule;
use common\logic\Feature\Feature\Feature;
use common\logic\Feature\Feature\FeatureModule;
use common\logic\Order\Order\Model\Order;
use common\logic\Producer\Producer\Module\ProducerModule;
use common\logic\User\User\Module\UserModule;
@@ -181,7 +181,7 @@ if (!Yii::$app->user->isGuest) {
'label' => '<span class="glyphicon glyphicon-envelope"></span> Contact',
'url' => $this->getUrlManagerProducer()->createUrl(['site/contact']),
'active' => $this->getControllerAction() == 'site/contact',
'visible' => $featureModule->getManager()->isEnabled(Feature::ALIAS_CONTACT)
'visible' => $featureModule->getChecker()->isEnabled(Feature::ALIAS_CONTACT)
],
],
]);

+ 123
- 124
producer/views/order/order.php View File

@@ -112,7 +112,7 @@ $this->setTitle('Commander');
></step-date>
<?php endif; ?>

<li id="step-products" :class="'col-md-3 '+((step == 'products') ? 'active ' : '')">
<li id="step-products" :class="'col-md-4 '+((step == 'products') ? 'active ' : '')">
<button @click="changeStep('products')"
:class="'btn '+ (step == 'products' ? 'btn-primary' : 'btn-default')"
:disabled="step == 'date' || step == 'point-sale'">
@@ -123,14 +123,14 @@ $this->setTitle('Commander');
{{ countProductOrdered() }} produit{{ (countProductOrdered() > 1) ? 's' : '' }}
</div>
</li>
<li id="step-payment" :class="'col-md-3 '+((step == 'payment') ? 'active' : '')">
<!--<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>
</li>-->
</ul>
<div class="clr"></div>
</div>
@@ -240,6 +240,9 @@ $this->setTitle('Commander');
</div>
<div class="comment" v-if="pointSale.infos && pointSale.infos.length > 0"
v-html="pointSale.infos"></div>
<div class="minimum-order-amount" v-if="pointSale.minimum_order_amount">
Montant minimum de commande : {{ formatPrice(pointSale.minimum_order_amount) }}
</div>
</td>
<td class="locality">{{ pointSale.locality }}</td>
<td class="actions">
@@ -325,8 +328,9 @@ $this->setTitle('Commander');
<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="product.photo"/>
<a class="product-photo" :href="product.photo_big" :title="product.name">
<img v-if="product.photo.length" class="photo-product" :src="product.photo"/>
</a>
</td>
<td class="name product-name-description-block">
<span class="name">{{ product.name }}</span>
@@ -425,137 +429,132 @@ $this->setTitle('Commander');
</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>
</div>
</transition>
<transition name="slide">
<div id="content-step-payment" v-if="step == 'payment'">
<div>
<div class="delivery">
<div class="delivery-home" v-if="pointSaleActive.is_home_delivery">
<label for="deliver-address">Adresse de livraison</label>
<textarea id="deliver-address" v-model="deliveryAddress" class="form-control"
required="required"></textarea>
</div>
</div>

<div class="comment">
<label for="order-comment">Commentaire</label>
<textarea id="order-comment" v-model="comment" class="form-control"></textarea>
</div>
<div id="content-step-payment">
<div>
<div class="delivery">
<div class="delivery-home" v-if="pointSaleActive.is_home_delivery">
<label for="deliver-address">Adresse de livraison</label>
<textarea id="deliver-address" v-model="deliveryAddress" class="form-control"
required="required"></textarea>
</div>
</div>

<template
v-if="producer.credit == 1 && pointSaleActive.credit == 1 && (pointSaleActive.credit_functioning == 'mandatory' || (pointSaleActive.credit_functioning == 'user' && user.credit_active)) && !checkCreditLimit(order) ">
<div class="alert alert-danger">
Vous devez
<template v-if="producer.online_payment == 1"><a
href="<?= \Yii::$app->urlManager->createUrl(['credit/add']) ?>">recharger
votre crédit</a></template>
<template v-else>recharger votre crédit</template>
auprès de votre producteur ou supprimer des produits.</span>
Votre producteur n'autorise pas un crédit inférieur
à <strong>{{ formatPrice(producer.credit_limit) }}</strong>.
</div>
<div class="block-actions">
<a class="btn btn-primary"
href="<?= \Yii::$app->urlManager->createUrl(['site/index']) ?>">Retour à
l'accueil</a>
</div>
</template>
<template v-else>
<div class="credit">
<div v-if="user && producer.credit == 1 && pointSaleActive.credit == 1 && (pointSaleActive.credit_functioning != 'user' || (pointSaleActive.credit_functioning == 'user' && user.credit_active))">
<input type="checkbox" id="use-credit" v-model="useCredit" disabled="disabled"
v-if="pointSaleActive.credit_functioning == 'mandatory' || (pointSaleActive.credit_functioning == 'user' && user.credit_active)"/>
<input type="checkbox" id="use-credit" v-model="useCredit" v-else/> <label
for="use-credit">Utiliser mon Crédit ({{ formatPrice(user.credit)
}})</label>
<div class="comment">
<label for="order-comment">Commentaire</label>
<textarea id="order-comment" v-model="comment" class="form-control"></textarea>
</div>

<template
v-if="producer.credit == 1 && pointSaleActive.credit == 1 && (pointSaleActive.credit_functioning == 'mandatory' || (pointSaleActive.credit_functioning == 'user' && user.credit_active)) && !checkCreditLimit(order) ">
<div class="alert alert-danger">
Vous devez
<template v-if="producer.online_payment == 1"><a
href="<?= \Yii::$app->urlManager->createUrl(['credit/add']) ?>">recharger
votre crédit</a></template>
<template v-else>recharger votre crédit</template>
auprès de votre producteur ou supprimer des produits.</span>
Votre producteur n'autorise pas un crédit inférieur
à <strong>{{ formatPrice(producer.credit_limit) }}</strong>.
</div>
<div class="block-actions">
<a class="btn btn-primary"
href="<?= \Yii::$app->urlManager->createUrl(['site/index']) ?>">Retour à
l'accueil</a>
</div>
</template>
<template v-else>
<div class="credit">
<div v-if="user && producer.credit == 1 && pointSaleActive.credit == 1 && (pointSaleActive.credit_functioning != 'user' || (pointSaleActive.credit_functioning == 'user' && user.credit_active))">
<input type="checkbox" id="use-credit" v-model="useCredit" disabled="disabled"
v-if="pointSaleActive.credit_functioning == 'mandatory' || (pointSaleActive.credit_functioning == 'user' && user.credit_active)"/>
<input type="checkbox" id="use-credit" v-model="useCredit" v-else/> <label
for="use-credit">Utiliser mon Crédit ({{ formatPrice(user.credit)
}})</label>

<div class="info" v-if="useCredit">
<template v-if="order == null || order.amount_paid == 0">
<span v-if="checkCreditLimit(order)">{{ priceTotal(true) }} seront débités</span>
<span v-else>
<div class="info" v-if="useCredit">
<template v-if="order == null || order.amount_paid == 0">
<span v-if="checkCreditLimit(order)">{{ priceTotal(true) }} seront débités</span>
<span v-else>
{{ formatPrice(user.credit) }} seront débités. (Limite de crédit à {{ formatPrice(producer.credit_limit) }})<br/>
Restera {{ formatPrice(priceTotal() - user.credit) }} à régler.
</span>
</template>
<template
v-else-if="order != null && order.amount_paid > 0 && order.amount_paid < priceTotal()">
<span v-if="checkCreditLimit(order)">{{ formatPrice(priceTotal() - order.amount_paid) }} seront débités</span>
<span v-else>
</template>
<template
v-else-if="order != null && order.amount_paid > 0 && order.amount_paid < priceTotal()">
<span v-if="checkCreditLimit(order)">{{ formatPrice(priceTotal() - order.amount_paid) }} seront débités</span>
<span v-else>
{{ formatPrice(user.credit) }} seront débités. (Limite de crédit à {{ formatPrice(producer.credit_limit) }})<br/>
Restera {{ formatPrice(priceTotal() - order.amount_paid - user.credit) }} à régler.
</span>
</template>
<template v-else-if="order != null && order.amount_paid > priceTotal()">
<span>{{ formatPrice(order.amount_paid - priceTotal()) }} seront remboursés</span>
</template>
</div>
</div>
<div v-else>
<span class="glyphicon glyphicon-chevron-right"></span>
<?php if ($producerModule->isOnlinePaymentActiveAndTypeOrder($producer)): ?>
La commande est à payer en ligne lors de l'étape suivante.
<?php elseif ($producer->option_payment_info && strlen($producer->option_payment_info) > 0): ?>
Confirmez votre commande et retrouvez les informations liées au paiement sur la page suivante.
<?php else: ?>
La commande sera à régler sur place.
<?php endif; ?>
</template>
<template v-else-if="order != null && order.amount_paid > priceTotal()">
<span>{{ formatPrice(order.amount_paid - priceTotal()) }} seront remboursés</span>
</template>
</div>
</div>
<div v-else>
<span class="glyphicon glyphicon-chevron-right"></span>
<?php if ($producerModule->isOnlinePaymentActiveAndTypeOrder($producer)): ?>
La commande est à payer en ligne lors de l'étape suivante.
<?php elseif ($producer->option_payment_info && strlen($producer->option_payment_info) > 0): ?>
Confirmez votre commande et retrouvez les informations liées au paiement sur la page suivante.
<?php else: ?>
La commande sera à régler sur place.
<?php endif; ?>
</div>
</div>
<div id="signup-guest" v-if="!user && producer.option_allow_order_guest">
<h3>Informations personnelles</h3>
<form action="#">
<div class="form-group field-signupguest-email required">
<label class="control-label" for="signupguest-email">Email</label>
<input type="email" id="signupguest-email" class="form-control"
name="SignupForm[email]">
<p class="help-block help-block-error"></p>
</div>
<div class="form-group field-signupguest-firstname required">
<label class="control-label" for="signupguest-firstname">Prénom</label>
<input type="text" id="signupguest-firstname" class="form-control"
name="SignupForm[firstname]">
<p class="help-block help-block-error"></p>
</div>
<div class="form-group field-signupguest-lastname required">
<label class="control-label" for="signupguest-lastname">Nom</label>
<input type="text" id="signupguest-lastname" class="form-control"
name="SignupForm[lastname]">
<p class="help-block help-block-error"></p>
</div>
<div class="form-group field-signupguest-phone required">
<label class="control-label" for="signupguest-phone">Téléphone</label>
<input type="text" id="signupguest-phone" class="form-control"
name="SignupForm[phone]">
<p class="help-block help-block-error"></p>
</div>
</form>
</div>
<div class="block-actions">
<button class="btn btn-primary" disabled="disabled" v-if="disableConfirmButton || !oneProductOrdered()">Je
confirme ma commande
</button>
<button class="btn btn-primary" v-else @click="confirmClick">Je confirme ma
commande
</button>
</div>
</template>
</div>
</div>
<div id="signup-guest" v-if="!user && producer.option_allow_order_guest">
<h3>Informations personnelles</h3>
<form action="#">
<div class="form-group field-signupguest-email required">
<label class="control-label" for="signupguest-email">Email</label>
<input type="email" id="signupguest-email" class="form-control"
name="SignupForm[email]">
<p class="help-block help-block-error"></p>
</div>
<!--<div class="form-group field-signupguest-password required">
<label class="control-label" for="signupguest-password">Mot de passe</label>
<input type="password" id="signupguest-password" class="form-control" name="SignupForm[password]">
<p class="help-block help-block-error"></p>
</div>-->
<div class="form-group field-signupguest-firstname required">
<label class="control-label" for="signupguest-firstname">Prénom</label>
<input type="text" id="signupguest-firstname" class="form-control"
name="SignupForm[firstname]">
<p class="help-block help-block-error"></p>
</div>
<div class="form-group field-signupguest-lastname required">
<label class="control-label" for="signupguest-lastname">Nom</label>
<input type="text" id="signupguest-lastname" class="form-control"
name="SignupForm[lastname]">
<p class="help-block help-block-error"></p>
</div>
<div class="form-group field-signupguest-phone required">
<label class="control-label" for="signupguest-phone">Téléphone</label>
<input type="text" id="signupguest-phone" class="form-control"
name="SignupForm[phone]">
<p class="help-block help-block-error"></p>
</div>
</form>
</div>
<div class="block-actions">
<button class="btn btn-primary" disabled="disabled" v-if="disableConfirmButton">Je
confirme ma commande
</button>
<button class="btn btn-primary" v-else @click="confirmClick">Je confirme ma
commande
</button>
</div>
</template>

</div>
<div class="alert alert-warning" v-else>
Aucun produit disponible
</div>
</div>
</div>
</transition>
<transition name="slide">

</transition>
</div>
</div>

@@ -647,7 +646,7 @@ $this->setTitle('Commander');
</div>

<script type="text/x-template" id="template-step-date">
<li id="step-date" :class="'col-md-3'+((step == 'date') ? ' active' : '')+(first ? ' first' : '')">
<li id="step-date" :class="'col-md-4'+((step == 'date') ? ' active' : '')+(first ? ' first' : '')">
<button @click="changeStep('date')" :class="'btn '+ (step == 'date' ? 'btn-primary' : 'btn-default')"
:disabled="producer && producer.option_order_entry_point == 'point-sale' && !pointSaleActive">
<span class="button-content"><span class="glyphicon glyphicon-time"></span> Date</span></span>
@@ -659,7 +658,7 @@ $this->setTitle('Commander');
</script>

<script type="text/x-template" id="template-step-point-sale">
<li id="step-point-sale" :class="'col-md-3'+((step == 'point-sale') ? ' active ' : '')+(first ? ' first' : '')">
<li id="step-point-sale" :class="'col-md-4'+((step == 'point-sale') ? ' active ' : '')+(first ? ' first' : '')">
<button @click="changeStep('point-sale')"
:class="'btn '+ (step == 'point-sale' ? 'btn-primary' : 'btn-default')"
:disabled="producer && (producer.option_order_entry_point == 'date' && step == 'date')">

+ 1
- 1
producer/views/site/index.php View File

@@ -157,7 +157,7 @@ $this->setPageTitle(Html::encode($producer->type . ' à ' . $producer->city));
'contentOptions' => ['class' => 'photo'],
'value' => function ($model) {
if (strlen($model->photo)) {
return '<img class="photo-product" src="'. Image::getThumbnailSmall($model->photo).'" />';
return '<a class="product-photo" href="'.Image::getThumbnailBig($model->photo).'" title="'.Html::encode($model->name).'"><img class="photo-product" src="'. Image::getThumbnailSmall($model->photo).'" />';
}
return '';
}

+ 36
- 34
producer/web/css/screen.css View File

@@ -1347,7 +1347,7 @@ termes.
padding-right: 0px;
}
/* line 72, ../sass/order/_order.scss */
.order-order #main #app-order-order #steps ul li#step-payment .btn::after, .order-order #main #app-order-order #steps ul li.first .btn::before {
.order-order #main #app-order-order #steps ul li#step-products .btn::after, .order-order #main #app-order-order #steps ul li.first .btn::before {
display: none;
}
/* line 77, ../sass/order/_order.scss */
@@ -1471,26 +1471,28 @@ termes.
font-weight: bold;
}
/* line 222, ../sass/order/_order.scss */
.order-order #main #app-order-order table#points-sale td.name .comment {
.order-order #main #app-order-order table#points-sale td.name .comment,
.order-order #main #app-order-order table#points-sale td.name .minimum-order-amount {
color: gray;
}
/* line 225, ../sass/order/_order.scss */
.order-order #main #app-order-order table#points-sale td.name .comment a {
/* line 226, ../sass/order/_order.scss */
.order-order #main #app-order-order table#points-sale td.name .comment a,
.order-order #main #app-order-order table#points-sale td.name .minimum-order-amount a {
color: #F39C12;
}
/* line 231, ../sass/order/_order.scss */
/* line 232, ../sass/order/_order.scss */
.order-order #main #app-order-order table#points-sale td.actions {
width: 150px;
}
/* line 233, ../sass/order/_order.scss */
/* line 234, ../sass/order/_order.scss */
.order-order #main #app-order-order table#points-sale td.actions button {
width: 100%;
}
/* line 239, ../sass/order/_order.scss */
/* line 240, ../sass/order/_order.scss */
.order-order #main #app-order-order table#points-sale tr.selected td {
background-color: white;
}
/* line 247, ../sass/order/_order.scss */
/* line 248, ../sass/order/_order.scss */
.order-order #main #app-order-order table#products td.category-name {
font-family: "highvoltageregular";
font-size: 22px;
@@ -1498,12 +1500,12 @@ termes.
text-transform: uppercase;
padding-top: 13px;
}
/* line 254, ../sass/order/_order.scss */
/* line 255, ../sass/order/_order.scss */
.order-order #main #app-order-order table#products td.category-name .glyphicon-triangle-bottom,
.order-order #main #app-order-order table#products td.category-name .glyphicon-triangle-right {
font-size: 15px;
}
/* line 259, ../sass/order/_order.scss */
/* line 260, ../sass/order/_order.scss */
.order-order #main #app-order-order table#products td.category-name span.label {
font-family: "Arial";
font-weight: normal;
@@ -1511,17 +1513,17 @@ termes.
text-transform: none;
margin-left: 15px;
}
/* line 268, ../sass/order/_order.scss */
/* line 269, ../sass/order/_order.scss */
.order-order #main #app-order-order table#products td.category-name:hover {
cursor: pointer;
background-color: #F39C12;
color: white;
}
/* line 276, ../sass/order/_order.scss */
/* line 277, ../sass/order/_order.scss */
.order-order #main #app-order-order table#products td.photo img {
width: 100px;
}
/* line 286, ../sass/order/_order.scss */
/* line 287, ../sass/order/_order.scss */
.order-order #main #app-order-order table#products .price-unit .decreasing-prices {
margin-top: 10px;
font-size: 10px;
@@ -1529,34 +1531,34 @@ termes.
padding-bottom: 2px;
margin-bottom: 0px;
}
/* line 294, ../sass/order/_order.scss */
/* line 295, ../sass/order/_order.scss */
.order-order #main #app-order-order table#products .price-unit .decreasing-prices ul li {
margin-bottom: 5px;
}
/* line 296, ../sass/order/_order.scss */
/* line 297, ../sass/order/_order.scss */
.order-order #main #app-order-order table#products .price-unit .decreasing-prices ul li strong {
font-weight: bold;
}
/* line 304, ../sass/order/_order.scss */
/* line 305, ../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: 135px;
text-align: center;
}
/* line 308, ../sass/order/_order.scss */
/* line 309, ../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 313, ../sass/order/_order.scss */
/* line 314, ../sass/order/_order.scss */
.order-order #main #app-order-order table#products .td-quantity {
width: 175px;
}
/* line 315, ../sass/order/_order.scss */
/* line 316, ../sass/order/_order.scss */
.order-order #main #app-order-order table#products .td-quantity input.quantity {
text-align: center;
border-right: 0px none;
}
/* line 319, ../sass/order/_order.scss */
/* line 320, ../sass/order/_order.scss */
.order-order #main #app-order-order table#products .td-quantity .input-group-addon {
padding: 5px;
padding-left: 0px;
@@ -1564,69 +1566,69 @@ termes.
border-left: 0px none;
border-right: 0px none;
}
/* line 330, ../sass/order/_order.scss */
/* line 331, ../sass/order/_order.scss */
.order-order #main #app-order-order table#products tr.total .summary h3 {
margin-top: 0px;
font-family: "capsuularegular";
text-transform: none;
margin-bottom: 5px;
}
/* line 337, ../sass/order/_order.scss */
/* line 338, ../sass/order/_order.scss */
.order-order #main #app-order-order table#products tr.total .summary ul {
margin-bottom: 15px;
padding-left: 20px;
font-size: 23px;
}
/* line 344, ../sass/order/_order.scss */
/* line 345, ../sass/order/_order.scss */
.order-order #main #app-order-order table#products tr.total .summary ul li .quantity {
font-size: 18px;
}
/* line 348, ../sass/order/_order.scss */
/* line 349, ../sass/order/_order.scss */
.order-order #main #app-order-order table#products tr.total .summary ul li .name {
font-family: "capsuularegular";
font-size: 24px;
}
/* line 352, ../sass/order/_order.scss */
/* line 353, ../sass/order/_order.scss */
.order-order #main #app-order-order table#products tr.total .summary ul li .other {
font-family: "capsuularegular";
font-size: 18px;
}
/* line 360, ../sass/order/_order.scss */
/* line 361, ../sass/order/_order.scss */
.order-order #main #app-order-order table#products tr.total .price-total {
font-size: 23px;
}
/* line 368, ../sass/order/_order.scss */
/* line 369, ../sass/order/_order.scss */
.order-order #main #app-order-order #content-step-payment .delivery {
margin-bottom: 20px;
}
/* line 371, ../sass/order/_order.scss */
/* line 372, ../sass/order/_order.scss */
.order-order #main #app-order-order #content-step-payment .delivery .delivery-home {
margin-bottom: 20px;
}
/* line 380, ../sass/order/_order.scss */
/* line 381, ../sass/order/_order.scss */
.order-order #main #app-order-order #content-step-payment .comment {
margin-bottom: 20px;
}
/* line 385, ../sass/order/_order.scss */
/* line 386, ../sass/order/_order.scss */
.order-order #main #app-order-order #content-step-payment .credit .info {
margin-left: 20px;
color: gray;
}
/* line 392, ../sass/order/_order.scss */
/* line 393, ../sass/order/_order.scss */
.order-order #main #app-order-order #specific-delays {
margin-top: 15px;
}
/* line 400, ../sass/order/_order.scss */
/* line 401, ../sass/order/_order.scss */
.order-order #main #app-order-order #infos {
margin-top: 30px;
}
/* line 402, ../sass/order/_order.scss */
/* line 403, ../sass/order/_order.scss */
.order-order #main #app-order-order #infos .panel-body {
padding-top: 0px;
white-space: pre-line;
}

/* line 412, ../sass/order/_order.scss */
/* line 413, ../sass/order/_order.scss */
#main #content .panel h3 {
font-family: "highvoltageregular";
margin: 0px;

+ 5
- 3
producer/web/js/producer.js View File

@@ -85,6 +85,8 @@ function opendistrib_products() {
$(this).hide();
$(this).parent().find('.content').show();
});

$('a.product-photo').simpleLightbox();
}

function opendistrib_datepicker() {
@@ -101,11 +103,11 @@ function opendistrib_fix_width_sidebar() {
}

function opendistrib_scroll(id) {
if ($("#" + id).size())
if ($("#" + id).size()) {
$('html,body').animate({
scrollTop: $("#" + id).offset().top
},
1000);
}, 500);
}
}

function opendistrib_base_url(with_slug) {

+ 11
- 1
producer/web/js/vuejs/order-order.js View File

@@ -509,6 +509,7 @@ var app = new Vue({
return thePriceWithTax;
}
},

confirmClick: function() {

var app = this ;
@@ -516,7 +517,16 @@ var app = new Vue({
// delivery
if(app.pointSaleActive.is_home_delivery && !app.deliveryAddress) {
this.errors = [] ;
this.errors.push('Veuillez saisir une adresse de livraison') ;
this.errors.push('Veuillez saisir une adresse de livraison.') ;
opendistrib_scroll('page-title');
return false ;
}

// montant minimum de commande
if(app.pointSaleActive.minimum_order_amount > 0 && app.priceTotal() < app.pointSaleActive.minimum_order_amount) {
this.errors = [] ;
this.errors.push('Le montant minimum de commande est de '+app.formatPrice(app.pointSaleActive.minimum_order_amount)+' pour ce point de vente.') ;
opendistrib_scroll('page-title');
return false ;
}


+ 3
- 2
producer/web/sass/order/_order.scss View File

@@ -69,7 +69,7 @@
padding-right: 0px ;
}
&#step-payment .btn::after,
&#step-products .btn::after,
&.first .btn::before {
display: none ;
}
@@ -219,7 +219,8 @@
font-weight: bold;
}

.comment {
.comment,
.minimum-order-amount {
color: gray;

a {

Loading…
Cancel
Save