Browse Source

Merge branch 'develop'

master
Guillaume Bourgeois 5 months ago
parent
commit
af9b014f4b
85 changed files with 2437 additions and 1283 deletions
  1. +137
    -0
      backend/controllers/AutomaticEmailController.php
  2. +34
    -67
      backend/controllers/CommunicateController.php
  3. +29
    -4
      backend/controllers/DistributionController.php
  4. +83
    -69
      backend/controllers/ReportController.php
  5. +7
    -0
      backend/controllers/UserController.php
  6. +35
    -0
      backend/forms/ReportPaymentsForm.php
  7. +2
    -282
      backend/models/MailForm.php
  8. +91
    -0
      backend/views/automatic-email/_form.php
  9. +7
    -24
      backend/views/automatic-email/create.php
  10. +102
    -0
      backend/views/automatic-email/index.php
  11. +8
    -15
      backend/views/automatic-email/update.php
  12. +1
    -1
      backend/views/communicate-admin/index.php
  13. +3
    -1
      backend/views/communicate/email.php
  14. +2
    -2
      backend/views/communicate/paper.php
  15. +13
    -1
      backend/views/dashboard-admin/index.php
  16. +6
    -5
      backend/views/dashboard/index.php
  17. +26
    -14
      backend/views/distribution/index.php
  18. +2
    -2
      backend/views/invoice/index.php
  19. +6
    -1
      backend/views/layouts/content.php
  20. +14
    -1
      backend/views/layouts/header.php
  21. +3
    -1
      backend/views/layouts/left.php
  22. +1
    -3
      backend/views/producer-admin/index.php
  23. +70
    -0
      backend/views/report/payments.php
  24. +2
    -2
      backend/views/sponsorship/index.php
  25. +1
    -1
      backend/views/support/index.php
  26. +11
    -0
      backend/views/user-admin/email_deliverability.php
  27. +29
    -12
      backend/views/user/_form.php
  28. +3
    -1
      backend/views/user/credit.php
  29. +112
    -93
      backend/web/css/screen.css
  30. +20
    -5
      backend/web/js/backend.js
  31. +51
    -25
      backend/web/js/vuejs/distribution-index.js
  32. +8
    -0
      backend/web/sass/automatic-email/_form.scss
  33. +25
    -16
      backend/web/sass/distribution/_index.scss
  34. +1
    -0
      backend/web/sass/screen.scss
  35. +4
    -0
      backend/web/sass/user/_form.scss
  36. +35
    -0
      common/components/ActiveDataProvider.php
  37. +7
    -6
      common/components/BulkMailer/BulkMailerBrevo.php
  38. +3
    -1
      common/components/BulkMailer/BulkMailerInterface.php
  39. +8
    -7
      common/components/BulkMailer/BulkMailerMailjet.php
  40. +3
    -2
      common/components/BulkMailer/BulkMailerProxy.php
  41. +2
    -0
      common/components/BusinessLogic.php
  42. +12
    -0
      common/components/BusinessLogicTrait.php
  43. +25
    -0
      common/components/Date.php
  44. +1
    -1
      common/components/UrlManagerCommon.php
  45. +1
    -1
      common/config/params.php
  46. +1
    -1
      common/versions/24.6.D.php
  47. +28
    -0
      common/versions/24.7.A.php
  48. +36
    -0
      console/commands/AutomaticEmailController.php
  49. +2
    -0
      console/config/main.php
  50. +26
    -0
      console/migrations/m240626_133528_add_column_user_exclusive_access_selected_points_sale.php
  51. +37
    -0
      console/migrations/m240627_064315_create_table_automatic_email.php
  52. +152
    -0
      domain/Communication/AutomaticEmail/AutomaticEmail.php
  53. +19
    -0
      domain/Communication/AutomaticEmail/AutomaticEmailBuilder.php
  54. +13
    -0
      domain/Communication/AutomaticEmail/AutomaticEmailDefinition.php
  55. +54
    -0
      domain/Communication/AutomaticEmail/AutomaticEmailManager.php
  56. +44
    -0
      domain/Communication/AutomaticEmail/AutomaticEmailModule.php
  57. +43
    -0
      domain/Communication/AutomaticEmail/AutomaticEmailRepository.php
  58. +15
    -0
      domain/Communication/AutomaticEmail/AutomaticEmailRepositoryQuery.php
  59. +36
    -0
      domain/Communication/AutomaticEmail/AutomaticEmailResolver.php
  60. +81
    -0
      domain/Communication/Email/ContactListResolver.php
  61. +67
    -0
      domain/Communication/Email/Email.php
  62. +27
    -0
      domain/Communication/Email/EmailBuilder.php
  63. +145
    -0
      domain/Communication/Email/EmailGenerator.php
  64. +43
    -0
      domain/Communication/Email/EmailModule.php
  65. +14
    -11
      domain/Document/DeliveryNote/DeliveryNoteSearch.php
  66. +18
    -16
      domain/Document/Invoice/InvoiceSearch.php
  67. +2
    -0
      domain/Feature/Feature/Feature.php
  68. +2
    -0
      domain/Feature/Feature/FeatureDefinition.php
  69. +60
    -0
      domain/Order/Order/OrderBuilder.php
  70. +78
    -13
      domain/Order/Order/OrderResolver.php
  71. +6
    -0
      domain/Producer/Producer/ProducerSolver.php
  72. +3
    -3
      domain/Product/Accessory/Accessory.php
  73. +19
    -0
      domain/Product/Product/ProductSolver.php
  74. +4
    -2
      domain/User/User/User.php
  75. +1
    -0
      domain/User/User/UserSearch.php
  76. +13
    -1
      domain/User/UserProducer/UserProducer.php
  77. +19
    -0
      domain/User/UserProducer/UserProducerSolver.php
  78. +9
    -11
      frontend/views/site/_button_producer_signup.php
  79. +4
    -4
      frontend/views/site/_prices_producer.php
  80. +0
    -5
      frontend/views/site/iamproducer.php
  81. +8
    -4
      frontend/views/site/service.php
  82. +45
    -28
      producer/controllers/OrderController.php
  83. +0
    -341
      producer/views/order/_form.php
  84. +3
    -3
      producer/views/order/order.php
  85. +214
    -174
      producer/web/js/vuejs/order-order.js

+ 137
- 0
backend/controllers/AutomaticEmailController.php View File

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

/**
* Copyright Souke (2018)
*
* contact@souke.fr
*
* 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\controllers\BackendController;
use domain\Communication\AutomaticEmail\AutomaticEmail;
use domain\Feature\Feature\Feature;
use domain\Product\Accessory\Accessory;
use yii\base\Response;
use yii\db\StaleObjectException;
use yii\filters\AccessControl;
use yii\web\NotFoundHttpException;

class AutomaticEmailController extends BackendController
{
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::class,
'rules' => [
[
'allow' => true,
'roles' => ['@'],
'matchCallback' => function ($rule, $action) {
return $this->getUserModule()->getAuthorizationChecker()
->isGrantedAsProducer($this->getUserCurrent())
&& $this->getFeatureModule()->getChecker()
->isEnabled(Feature::ALIAS_AUTOMATIC_EMAIL);
}
]
],
],
];
}

public function actionIndex()
{
return $this->render('index', [
'dataProvider' => $this->getAutomaticEmailModule()->getRepository()
->queryAutomaticEmails()->getDataProvider(20),
]);
}

public function actionCreate()
{
$automaticEmailModule = $this->getAutomaticEmailModule();
$automaticEmailModel = $automaticEmailModule->getBuilder()->instanciateAutomaticEmail($this->getProducerCurrent());

if ($automaticEmailModel->load(\Yii::$app->request->post()) && $automaticEmailModel->validate()) {
$automaticEmail = $automaticEmailModule->getManager()->createAutomaticEmail(
$this->getProducerCurrent(),
$automaticEmailModel->getDay(),
$automaticEmailModel->getDelayBeforeDistribution(),
$automaticEmailModel->getSubject(),
$automaticEmailModel->getMessage(),
$automaticEmailModel->getIntegrateProductList()
);
$this->setFlash('success', "Email automatique ajouté");
return $this->redirectAfterSave('automatic-email', $automaticEmail->getId());
}

return $this->render('create', [
'automaticEmail' => $automaticEmailModel
]);
}

public function actionUpdate(int $id)
{
$automaticEmail = $this->findAutomaticEmail($id);

if ($automaticEmail->load(\Yii::$app->request->post()) && $automaticEmail->validate() && $automaticEmail->save()) {
$this->setFlash('success', "Email automatique modifié");
return $this->redirectAfterSave('automatic-email', $automaticEmail->getId());
}

return $this->render('update', [
'automaticEmail' => $automaticEmail
]);
}

public function actionDelete(int $id): Response
{
$automaticEmail = $this->findAutomaticEmail($id);

if($automaticEmail->delete()) {
$this->setFlash('success', "Email automatique supprimé");
}

return $this->redirect('index');
}

protected function findAutomaticEmail($id): AutomaticEmail
{
if (($automaticEmail = $this->getAutomaticEmailModule()->getRepository()->findOneAutomaticEmailById($id)) !== null) {
return $automaticEmail;
} else {
throw new NotFoundHttpException("L'email automatique n'a pas été trouvé.");
}
}
}

+ 34
- 67
backend/controllers/CommunicateController.php View File

@@ -40,8 +40,10 @@ namespace backend\controllers;

use backend\models\MailForm;
use common\helpers\GlobalParam;
use kartik\mpdf\Pdf;
use domain\Communication\Email\ContactListResolver;
use domain\Communication\Email\EmailGenerator;
use domain\PointSale\PointSale\PointSale;
use kartik\mpdf\Pdf;
use yii\filters\AccessControl;
use yii\filters\VerbFilter;

@@ -84,91 +86,56 @@ class CommunicateController extends BackendController
$usersPointSaleHasOrder = 0,
$idDistribution = 0)
{
$producerCurrent = $this->getProducerCurrent();
$userModule = $this->getUserModule();
$distributionModule = $this->getDistributionModule();
$emailModule = $this->getEmailModule();

$mailForm = new MailForm();
// Sujet et message par défaut
$mailForm->subject = $this->getProducerModule()->getSolver()->getConfig('option_communicate_email_default_subject');
$mailForm->message = $this->getProducerModule()->getSolver()->getConfig('option_communicate_email_default_message');

if ($idPointSale && !$usersPointSaleLink && !$usersPointSaleHasOrder) {
$usersPointSaleLink = 1;
}
if ($idDistribution && !$usersPointSaleLink && !$usersPointSaleHasOrder) {
$usersPointSaleLink = 1;
}

if($idDistribution) {
$users = [];
$distribution = $distributionModule->getRepository()->findOneDistributionById($idDistribution);
if($distribution) {
$mailForm->id_distribution = $idDistribution;
foreach($distribution->pointSaleDistribution as $pointSaleDistribution) {
if($pointSaleDistribution->delivery) {
$usersPointSaleArray = $userModule->getRepository()->queryUsersBy([
'id_producer' => $producerCurrent->id,
'id_point_sale' => $pointSaleDistribution->id_point_sale,
'users_point_sale_link' => $usersPointSaleLink,
'users_point_sale_has_order' => $usersPointSaleHasOrder,
'newsletter' => true
])->all();

foreach($usersPointSaleArray as $user) {
$users[$user['id']] = $user;
}
}
}
}
}
else {
$users = $userModule->queryUsersBy([
'id_producer' => GlobalParam::getCurrentProducerId(),
'id_point_sale' => $idPointSale,
'users_point_sale_link' => $usersPointSaleLink,
'users_point_sale_has_order' => $usersPointSaleHasOrder,
'subscribers' => $sectionSubscribers,
'inactive' => $sectionInactiveUsers,
'newsletter' => true
])->all();
}

$usersArray = [];
foreach ($users as $key => $user) {
if (isset($user['email']) && strlen($user['email']) > 0) {
if($this->getProducerCurrent()->isDemoAccount()) {
$usersArray[] = \Yii::$app->faker->email();
}
else {
$usersArray[] = $user['email'];
}
} else {
unset($users[$key]);
}
}

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

$pointSale = null;
if ($idPointSale) {
$pointSale = PointSale::findOne(['id' => $idPointSale]);
$pointSale = $idPointSale ? $this->getPointSaleModule()->getRepository()->findOnePointSaleById($idPointSale) : null;
$distribution = $idDistribution ? $this->getDistributionModule()->getRepository()->findOneDistributionById($idDistribution) : null;
if($distribution) {
$mailForm->id_distribution = $distribution->id;
}

$usersArray = $emailModule->getContactListResolver()->search(
$this->getProducerCurrent(),
$distribution,
$pointSale,
$usersPointSaleLink,
$usersPointSaleHasOrder,
$sectionSubscribers,
$sectionInactiveUsers
);

if ($mailForm->load(\Yii::$app->request->post()) && $mailForm->validate()) {

if($this->getProducerCurrent()->isDemoAccount()) {
$this->setFlash('error', "Fonctionnalité désactivée sur le compte de démo.");
}
else {
$mailForm->sendEmail($users);
$distribution = $mailForm->id_distribution
? $this->getDistributionModule()->getRepository()->findOneDistributionById($mailForm->id_distribution)
: null;

$email = $emailModule->getGenerator()->createEmail(
$mailForm->subject,
$mailForm->message,
$mailForm->integrate_product_list,
$this->getProducerCurrent(),
$distribution
);
$emailModule->getBulkMailer()->sendEmail($email, $usersArray);

$this->setFlash('success', 'Votre email a bien été envoyé.');
}

return $this->redirect(['email', 'idPointSale' => $idPointSale]);
}

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

$incomingDistributionsArray = $this->getDistributionModule()->getRepository()->findDistributionsIncoming();
$incomingDistributionsDatesArray = ['0' => '--'];
foreach ($incomingDistributionsArray as $distribution) {
$incomingDistributionsDatesArray[$distribution->id] = strftime('%A %d %B %Y', strtotime($distribution->date));

+ 29
- 4
backend/controllers/DistributionController.php View File

@@ -227,8 +227,18 @@ class DistributionController extends BackendController
}

$jsonProduct['quantity_max'] = $orderModule->getResolver()->getProductQuantityMax($product, $distribution);
$jsonProduct['quantity_remaining'] = $orderModule->getResolver()->getProductQuantityRemaining($product, $distribution);

$jsonProduct['quantity_form'] = 0;

$jsonProduct['accessories'] = [];
foreach($product->getProductAccessories() as $productAccessory) {
$jsonProduct['accessories'][] = [
'id_accessory' => $productAccessory->getAccessory()->getId(),
'quantity' => $productAccessory->getQuantity(),
];
}

if ($product->taxRate) {
$jsonProduct['taxRate'] = $product->taxRate->getAttributes();
}
@@ -530,11 +540,14 @@ class DistributionController extends BackendController
$idDistribution,
$idUser = false,
$idPointSale = false,
$idOrder = false
$idOrder = false,
$productOrderFormArray = []
)
{
\Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;

$productOrderFormArray = json_decode($productOrderFormArray, true);

$distributionModule = $this-> getDistributionModule();
$orderModule = $this->getOrderModule();
$userModule = $this->getUserModule();
@@ -565,7 +578,7 @@ class DistributionController extends BackendController

if (isset($order->productOrder)) {
foreach ($order->productOrder as $productOrder) {
if ($productOrder->id_product == $product['id']) {
if ($productOrder->id_product == $product->id) {
if ($productOrder->invoice_price) {
$invoicePrice = number_format($productOrder->invoice_price, 5);
} else {
@@ -576,16 +589,28 @@ class DistributionController extends BackendController
}
}

$productOrderArray[$product['id']] = [
// Quantité définie dans le formulaire
if(isset($productOrderFormArray[$product->id]['quantity'])) {
$quantity = $productOrderFormArray[$product->id]['quantity'] / $productModule->getSolver()->getUnitCoefficient($product);
}

$productOrderArray[$product->id] = [
'quantity' => $quantity,
'unit' => $product->unit,
'prices' => $priceArray,
'active' => $product->productDistribution[0]->active
&& (!$pointSale || $productModule->isAvailableOnPointSale($product, $pointSale)),
'invoice_price' => $invoicePrice
'invoice_price' => $invoicePrice,
];
}

// construction de $orderOverride
$orderOverride = $orderModule->getBuilder()->instanciateOrderFromProductOrdersArray($productOrderArray, $order);
foreach($productOrderArray as $idProduct => $productOrder) {
$product = $productModule->getRepository()->findOneProductById($idProduct);
$productOrderArray[$idProduct]['quantity_remaining'] = $orderModule->getResolver()->getProductQuantityRemaining($product, $distribution, $orderOverride);
}

return $productOrderArray;
}


+ 83
- 69
backend/controllers/ReportController.php View File

@@ -44,6 +44,7 @@ use common\helpers\MeanPayment;
use common\helpers\Price;
use domain\Order\Order\OrderRepositoryQuery;
use domain\Order\OrderStatus\OrderStatus;
use backend\forms\ReportPaymentsForm;
use Yii;
use yii\filters\AccessControl;

@@ -198,85 +199,98 @@ class ReportController extends BackendController
return $condition;
}

public function actionPayments(string $dateStart, string $dateEnd)
public function actionPayments()
{
$orderModule = $this->getOrderModule();
$ordersArray = $orderModule->getRepository()
->findOrdersByPeriod(
new \DateTime($dateStart),
new \DateTime($dateEnd)
);

$datas = [
[
'Date',
'Client',
'Montant',
'Facture',
'Crédit',
'Espèce',
'Chèque',
'Virement',
'Carte bancaire',
'Total'
]
];
$dateStart = $dateEnd = null;
$reportPaymentsForm = new ReportPaymentsForm();
if($reportPaymentsForm->load(Yii::$app->request->post()) && $reportPaymentsForm->validate()) {
$dateStart = date('Y-m-d', strtotime(str_replace('/', '-', $reportPaymentsForm->date_start)));
$dateEnd = date('Y-m-d', strtotime(str_replace('/', '-', $reportPaymentsForm->date_end)));
}

if($dateStart && $dateEnd) {
$orderModule = $this->getOrderModule();
$ordersArray = $orderModule->getRepository()
->findOrdersByPeriod(
new \DateTime($dateStart),
new \DateTime($dateEnd)
);

$sumAmountTotalSpentByCredit = 0;
$sumAmountTotalSpentByMoney = 0;
$sumAmountTotalSpentByCheque = 0;
$sumAmountTotalSpentByTransfer = 0;
$sumAmountTotalSpentByCreditCard = 0;
$datas = [
[
'Date',
'Client',
'Montant',
'Facture',
'Crédit',
'Espèce',
'Chèque',
'Virement',
'Carte bancaire',
'Total'
]
];

foreach($ordersArray as $order) {
$orderModule->getBuilder()->initOrder($order);
$sumAmountTotalSpentByCredit = 0;
$sumAmountTotalSpentByMoney = 0;
$sumAmountTotalSpentByCheque = 0;
$sumAmountTotalSpentByTransfer = 0;
$sumAmountTotalSpentByCreditCard = 0;

$hasInvoice = false;
$referenceInvoice = 'Non';
if($order->invoice) {
$hasInvoice = true;
$referenceInvoice = $order->invoice->reference."";
}
foreach($ordersArray as $order) {
$orderModule->getBuilder()->initOrder($order);

$hasInvoice = false;
$referenceInvoice = 'Non';
if($order->invoice) {
$hasInvoice = true;
$referenceInvoice = $order->invoice->reference."";
}

$amountTotalSpentByCredit = $orderModule->getSolver()->getAmountTotalSpentByMeanPayment($order, MeanPayment::CREDIT);
$sumAmountTotalSpentByCredit += $amountTotalSpentByCredit;
$amountTotalSpentByMoney = $orderModule->getSolver()->getAmountTotalSpentByMeanPayment($order, MeanPayment::MONEY);
$sumAmountTotalSpentByMoney += $amountTotalSpentByMoney;
$amountTotalSpentByCheque = $orderModule->getSolver()->getAmountTotalSpentByMeanPayment($order, MeanPayment::CHEQUE);
$sumAmountTotalSpentByCheque += $amountTotalSpentByCheque;
$amountTotalSpentByTransfer = $orderModule->getSolver()->getAmountTotalSpentByMeanPayment($order, MeanPayment::TRANSFER);
$sumAmountTotalSpentByTransfer += $amountTotalSpentByTransfer;
$amountTotalSpentByCreditCard = $orderModule->getSolver()->getAmountTotalSpentByMeanPayment($order, MeanPayment::CREDIT_CARD);
$sumAmountTotalSpentByCreditCard += $amountTotalSpentByCreditCard;
$amountTotalSpentByCredit = $orderModule->getSolver()->getAmountTotalSpentByMeanPayment($order, MeanPayment::CREDIT);
$sumAmountTotalSpentByCredit += $amountTotalSpentByCredit;
$amountTotalSpentByMoney = $orderModule->getSolver()->getAmountTotalSpentByMeanPayment($order, MeanPayment::MONEY);
$sumAmountTotalSpentByMoney += $amountTotalSpentByMoney;
$amountTotalSpentByCheque = $orderModule->getSolver()->getAmountTotalSpentByMeanPayment($order, MeanPayment::CHEQUE);
$sumAmountTotalSpentByCheque += $amountTotalSpentByCheque;
$amountTotalSpentByTransfer = $orderModule->getSolver()->getAmountTotalSpentByMeanPayment($order, MeanPayment::TRANSFER);
$sumAmountTotalSpentByTransfer += $amountTotalSpentByTransfer;
$amountTotalSpentByCreditCard = $orderModule->getSolver()->getAmountTotalSpentByMeanPayment($order, MeanPayment::CREDIT_CARD);
$sumAmountTotalSpentByCreditCard += $amountTotalSpentByCreditCard;

$datas[] = [
$order->distribution->date,
$orderModule->getSolver()->getOrderUsername($order),
CSV::formatNumber($orderModule->getSolver()->getOrderAmountWithTax($order)),
$referenceInvoice,
$hasInvoice ? '' : CSV::formatNumber($amountTotalSpentByCredit),
$hasInvoice ? '' : CSV::formatNumber($amountTotalSpentByMoney),
$hasInvoice ? '' : CSV::formatNumber($amountTotalSpentByCheque),
$hasInvoice ? '' : CSV::formatNumber($amountTotalSpentByTransfer),
$hasInvoice ? '' : CSV::formatNumber($amountTotalSpentByCreditCard)
];
}

$datas[] = [
$order->distribution->date,
$orderModule->getSolver()->getOrderUsername($order),
CSV::formatNumber($orderModule->getSolver()->getOrderAmountWithTax($order)),
$referenceInvoice,
$hasInvoice ? '' : CSV::formatNumber($amountTotalSpentByCredit),
$hasInvoice ? '' : CSV::formatNumber($amountTotalSpentByMoney),
$hasInvoice ? '' : CSV::formatNumber($amountTotalSpentByCheque),
$hasInvoice ? '' : CSV::formatNumber($amountTotalSpentByTransfer),
$hasInvoice ? '' : CSV::formatNumber($amountTotalSpentByCreditCard)
'',
'',
'',
'Totaux paiements',
CSV::formatNumber($sumAmountTotalSpentByCredit),
CSV::formatNumber($sumAmountTotalSpentByMoney),
CSV::formatNumber($sumAmountTotalSpentByCheque),
CSV::formatNumber($sumAmountTotalSpentByTransfer),
CSV::formatNumber($sumAmountTotalSpentByCreditCard),
CSV::formatNumber($sumAmountTotalSpentByCredit + $sumAmountTotalSpentByMoney + $sumAmountTotalSpentByCheque +
$sumAmountTotalSpentByTransfer + $sumAmountTotalSpentByCreditCard)
];
}

$datas[] = [
'',
'',
'',
'Totaux paiements',
CSV::formatNumber($sumAmountTotalSpentByCredit),
CSV::formatNumber($sumAmountTotalSpentByMoney),
CSV::formatNumber($sumAmountTotalSpentByCheque),
CSV::formatNumber($sumAmountTotalSpentByTransfer),
CSV::formatNumber($sumAmountTotalSpentByCreditCard),
CSV::formatNumber($sumAmountTotalSpentByCredit + $sumAmountTotalSpentByMoney + $sumAmountTotalSpentByCheque +
$sumAmountTotalSpentByTransfer + $sumAmountTotalSpentByCreditCard)
];
CSV::send('commandes.csv', $datas);
}

CSV::send('commandes.csv', $datas);
return $this->render('payments', [
'reportPaymentsForm' => $reportPaymentsForm
]);
}

}

+ 7
- 0
backend/controllers/UserController.php View File

@@ -159,6 +159,7 @@ class UserController extends BackendController
);
$userCreate->points_sale = $model->points_sale;
$userCreate->user_groups = $model->user_groups;
$userCreate->exclusive_access_selected_points_sale = $model->exclusive_access_selected_points_sale;

$this->processLinkPointSale($userCreate);
$this->processLinkUserGroup($userCreate);
@@ -220,6 +221,7 @@ class UserController extends BackendController
$model->newsletter = $userBelongToProducer->newsletter;
$model->trust_alert = $userBelongToProducer->trust_alert;
$model->trust_alert_comment = $userBelongToProducer->trust_alert_comment;
$model->exclusive_access_selected_points_sale = $userBelongToProducer->exclusive_access_selected_points_sale;

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

@@ -420,6 +422,11 @@ class UserController extends BackendController
}
}
}

// Accès exclusif aux points de vente sélectionnés
$userProducer = UserProducer::findOne(['id_user' => $modelUser->id, 'id_producer' => GlobalParam::getCurrentProducerId()]);
$userProducer->setExclusiveAccessSelectedPointsSale($modelUser->exclusive_access_selected_points_sale);
$userProducer->save();
}

/**

+ 35
- 0
backend/forms/ReportPaymentsForm.php View File

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

namespace backend\forms;

use yii\base\Model;

/**
* UploadForm is the model behind the upload form.
*/
class ReportPaymentsForm extends Model
{
var $date_start;
var $date_end;

/**
* @return array the validation rules.
*/
public function rules()
{
return [
[['date_start', 'date_end'], 'required'],
[['date_start', 'date_end'], 'safe'],
];
}

public function attributeLabels()
{
return [
'date_start' => "Date de début",
'date_end' => "Date de fin"
];
}
}

?>

+ 2
- 282
backend/models/MailForm.php View File

@@ -39,20 +39,10 @@ termes.
namespace backend\models;

use common\helpers\GlobalParam;
use common\helpers\Mailjet;
use common\helpers\Price;
use domain\Config\Unit\UnitDefinition;
use domain\Distribution\Distribution\Distribution;
use domain\Communication\Email\EmailGenerator;
use domain\Distribution\Distribution\DistributionModule;
use domain\Producer\Producer\ProducerModule;
use domain\Product\Product\Product;
use domain\Product\Product\ProductModule;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Response;
use Psr\Http\Message\ResponseInterface;
use Yii;
use yii\base\Model;
use yii\helpers\Html;

/**
* ContactForm is the model behind the contact form.
@@ -62,7 +52,7 @@ class MailForm extends Model
public $id_distribution ;
public $subject;
public $message;
public $integrate_product_list = false;
public $integrate_product_list;

/**
* @inheritdoc
@@ -88,274 +78,4 @@ class MailForm extends Model
'integrate_product_list' => 'Intégrer la liste des produits au message'
];
}

public function sendEmail($contactsArray, $fromProducer = true)
{
$productModule = ProductModule::getInstance();
$producerModule = ProducerModule::getInstance();
$distributionModule = DistributionModule::getInstance();

$messageAutoText = '' ;
$messageAutoHtml = '' ;

$messageAutoHtml .= ' <style type="text/css">
h1, h2, h3, h4, h5, h6 {
padding: 0px;
margin: 0px;
margin-bottom: 10px;
}
p {
margin: 0px;
padding: 0px;
margin-bottom: 5px;
}
</style>';

if($this->id_distribution) {

$messageAutoText = '

' ;
$messageAutoHtml .= '<br /><br />' ;

$distribution = Distribution::searchOne(['id' => $this->id_distribution]) ;

if($distribution) {

$linkOrder = $distributionModule->getLinkOrder($distribution);
$dateOrder = strftime('%A %d %B %Y', strtotime($distribution->date)) ;
$messageAutoHtml .= '<a href="'.$linkOrder.'">Passer ma commande du '.$dateOrder.'</a>' ;
$messageAutoText .= 'Suivez ce lien pour passer votre commande du '.$dateOrder.' :
'.$linkOrder ;

if($this->integrate_product_list) {
$productsArray = Product::find()
->where([
'id_producer' => GlobalParam::getCurrentProducerId(),
])
->andWhere('status >= :status')
->addParams(['status' => Product::STATUS_OFFLINE])
->innerJoinWith(['productDistribution' => function($query) use($distribution) {
$query->andOnCondition([
'product_distribution.id_distribution' => $distribution->id,
'product_distribution.active' => 1
]);
}])
->orderBy('product.name ASC')
->all();

if(count($productsArray) > 1) {
$messageAutoHtml .= '<br /><br />Produits disponibles : <br /><ul>' ;
$messageAutoText .= '

Produits disponibles :
' ;
foreach($productsArray as $product) {

$productDescription = $product->name ;
if(strlen($product->description)) {
$productDescription .= ' / '.$product->description ;
}
if($product->price) {
$productDescription .= ' / '.Price::format($productModule->getPriceWithTax($product)) ;
$productDescription .= ' ('. $productModule->getSolver()->strUnit($product, UnitDefinition::WORDING_UNIT).')' ;
}

$messageAutoText .= '- '.$productDescription.'
' ;
$messageAutoHtml .= '<li>'.Html::encode($productDescription).'</li>' ;
}
$messageAutoHtml .= '</ul>' ;
}
}
}
}

if($fromProducer) {
$producer = GlobalParam::getCurrentProducer() ;
$fromEmail = $producerModule->getProducerEmailPlatform($producer) ;
$fromName = $producer->name ;

$linkProducer = 'https://'.$producer->slug.'.souke.fr';
$linkUnsubscribe = Yii::$app->urlManagerProducer->createAbsoluteUrl(['newsletter/unsubscribe', 'slug_producer' => $producer->slug]);

// Message inscription newsletter
$messageAutoText .= "

--
Boutique : ".$linkProducer."
Me désinscrire : ".$linkUnsubscribe;

$messageAutoHtml .= "<br /><br />--<br>";
$messageAutoHtml .= "Boutique : <a href=\"".$linkProducer."\">".$linkProducer."</a><br>";
$messageAutoHtml .= "Me désinscrire : <a href=\"".$linkUnsubscribe."\">".$linkUnsubscribe."</a>";
}
else {
$fromEmail = 'contact@souke.fr' ;
$fromName = 'Souke' ;
}

$subject = $this->subject;
$htmlContent = nl2br($this->message).$messageAutoHtml;
$textContent = $this->message.$messageAutoText;

Yii::$app->bulkMailer->sendEmails($contactsArray, $fromName, $fromEmail, $subject, $htmlContent, $textContent);
}

/*

public function sendEmailMailjet($usersArray, $fromProducer = true)
{
$productModule = ProductModule::getInstance();
$producerModule = ProducerModule::getInstance();
$distributionModule = DistributionModule::getInstance();

$mj = new \Mailjet\Client(
Mailjet::getApiKey('public'),
Mailjet::getApiKey('private'),
true,
['version' => 'v3.1']
);

$messageAutoText = '' ;
$messageAutoHtml = '' ;

$messageAutoHtml .= ' <style type="text/css">
h1, h2, h3, h4, h5, h6 {
padding: 0px;
margin: 0px;
margin-bottom: 10px;
}

p {
margin: 0px;
padding: 0px;
margin-bottom: 5px;
}
</style>';

if($this->id_distribution) {

$messageAutoText = '

' ;
$messageAutoHtml .= '<br /><br />' ;

$distribution = Distribution::searchOne(['id' => $this->id_distribution]) ;

if($distribution) {

$linkOrder = $distributionModule->getLinkOrder($distribution);
$dateOrder = strftime('%A %d %B %Y', strtotime($distribution->date)) ;
$messageAutoHtml .= '<a href="'.$linkOrder.'">Passer ma commande du '.$dateOrder.'</a>' ;
$messageAutoText .= 'Suivez ce lien pour passer votre commande du '.$dateOrder.' :
'.$linkOrder ;

if($this->integrate_product_list) {
$productsArray = Product::find()
->where([
'id_producer' => GlobalParam::getCurrentProducerId(),
])
->andWhere('status >= :status')
->addParams(['status' => Product::STATUS_OFFLINE])
->innerJoinWith(['productDistribution' => function($query) use($distribution) {
$query->andOnCondition([
'product_distribution.id_distribution' => $distribution->id,
'product_distribution.active' => 1
]);
}])
->orderBy('product.name ASC')
->all();

if(count($productsArray) > 1) {
$messageAutoHtml .= '<br /><br />Produits disponibles : <br /><ul>' ;
$messageAutoText .= '

Produits disponibles :
' ;
foreach($productsArray as $product) {

$productDescription = $product->name ;
if(strlen($product->description)) {
$productDescription .= ' / '.$product->description ;
}
if($product->price) {
$productDescription .= ' / '.Price::format($productModule->getPriceWithTax($product)) ;
$productDescription .= ' ('. $productModule->getSolver()->strUnit($product, UnitDefinition::WORDING_UNIT).')' ;
}

$messageAutoText .= '- '.$productDescription.'
' ;
$messageAutoHtml .= '<li>'.Html::encode($productDescription).'</li>' ;
}
$messageAutoHtml .= '</ul>' ;
}
}
}
}

if($fromProducer) {
$producer = GlobalParam::getCurrentProducer() ;
$fromEmail = $producerModule->getProducerEmailPlatform($producer) ;
$fromName = $producer->name ;

$linkProducer = 'https://'.$producer->slug.'.souke.fr';
$linkUnsubscribe = Yii::$app->urlManagerProducer->createAbsoluteUrl(['newsletter/unsubscribe', 'slug_producer' => $producer->slug]);

// Message inscription newsletter
$messageAutoText .= "

--
Boutique : ".$linkProducer."
Me désinscrire : ".$linkUnsubscribe;

$messageAutoHtml .= "<br /><br />--<br>";
$messageAutoHtml .= "Boutique : <a href=\"".$linkProducer."\">".$linkProducer."</a><br>";
$messageAutoHtml .= "Me désinscrire : <a href=\"".$linkUnsubscribe."\">".$linkUnsubscribe."</a>";
}
else {
$fromEmail = 'contact@souke.fr' ;
$fromName = 'Souke' ;
}

$body = ['Messages' => []] ;

foreach($usersArray as $user) {
$body['Messages'][] = [
'From' => [
'Email' => $fromEmail,
'Name' => $fromName
],
'To' => [
[
'Email' => $user['email'],
'Name' => $user['name'].' '.$user['lastname']
]
],
'Subject' => $this->subject,
'TextPart' => $this->message.$messageAutoText,
'HTMLPart' => nl2br($this->message).$messageAutoHtml
] ;

if(count($body['Messages']) == 50) {
$response = $mj->post(\Mailjet\Resources::$Email, ['body' => $body]);
$body['Messages'] = [] ;
}
}

if(count($body['Messages']) > 0) {
$response = $mj->post(\Mailjet\Resources::$Email, ['body' => $body]);
}

$success = (isset($response) && $response) ? $response->success() : false ;

if(!$success) {
Yii::error($response->getBody(), 'Mailjet');
}

return $response ;
}

*/
}

+ 91
- 0
backend/views/automatic-email/_form.php View File

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

/**
* Copyright Souke (2018)
*
* contact@souke.fr
*
* 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\components\Date;
use common\helpers\Dropdown;
use lo\widgets\Toggle;
use yii\widgets\ActiveForm;

?>

<div class="automatic-email-form">
<?php $form = ActiveForm::begin(); ?>
<div class="col-md-8">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
<i class="fa fa-th-list"></i>
Général
</h3>
</div>
<div class="panel-body">
<?= $form->field($automaticEmail, 'status')->widget(Toggle::class,
[
'options' => [
'data-id' => $automaticEmail->id,
'data-on' => 'Activé',
'data-off' => 'Désactivé'
],
]);
?>
<?= $form->field($automaticEmail, 'day')->dropDownList(Date::getDaysOfWeekArray()) ?>
<?= $form->field($automaticEmail, 'delay_before_distribution')->dropDownList(Dropdown::numberChoices(1, 7, false, ' jour(s) avant')); ?>
<?= $form->field($automaticEmail, 'subject')->textInput() ?>
<?= $form->field($automaticEmail, 'message')->widget(letyii\tinymce\Tinymce::class, [
'configs' => [
'plugins' => Yii::$app->parameterBag->get('tinyMcePlugins'),
]
]); ?>
<?= $form->field($automaticEmail, 'integrate_product_list')->widget(Toggle::class,
[
'options' => [
'data-id' => $automaticEmail->id,
'data-on' => 'Oui',
'data-off' => 'Non',
'data-offstyle' => 'default',
],
]);
?>
</div>
</div>
</div>
<?= $this->render('@backend/views/_include/form_actions.php',[
'model' => $automaticEmail,
]); ?>
<?php ActiveForm::end(); ?>
</div>

producer/views/order/update.php → backend/views/automatic-email/create.php View File

@@ -36,31 +36,14 @@ pris connaissance de la licence CeCILL, et que vous en avez accepté les
termes.
*/

$this->setTitle('Modifier une commande') ;
$this->setTitle("Ajouter un email automatique") ;
$this->addBreadcrumb(['label' => "Emails automatiques", 'url' => ['index']]) ;
$this->addBreadcrumb('Ajouter') ;

?>
<div class="order-update">
<?php if($orderNotfound): ?>
<div class="alert alert-danger">Cette commande est introuvable</div><br />
<a class="btn btn-default" href="<?php echo \Yii::$app->urlManager->createUrl(['order/index']); ?>">Retour</a>
<?php else: ?>
<?= $this->render('_form', [
'model' => $model,
'pointsSaleArray' => $pointsSaleArray,
'distributionDaysArray' => $distributionDaysArray,
'productsArray' => $productsArray,
'selectedProducts' => $selectedProducts,
'availableProducts' => $availableProducts,
'distribution' => $distribution,
'ordersArray' => $ordersArray,
'producersArray' => $producersArray,
'idProducer' => $idProducer,
'producer' => $producer,
'credit' => $credit
]) ?>
<?php endif; ?>

<div class="automatic-email-create">
<?= $this->render('_form', [
'automaticEmail' => $automaticEmail,
]) ?>
</div>

+ 102
- 0
backend/views/automatic-email/index.php View File

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

/**
* Copyright Souke (2018)
*
* contact@souke.fr
*
* 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\components\View;
use domain\Communication\AutomaticEmail\AutomaticEmail;
use yii\helpers\Html;
use yii\grid\GridView;

/**
* @var View $this
*/

$this->setTitle('Emails automatiques');
$this->addBreadcrumb($this->getTitle());
$this->addButton(['label' => 'Nouvel email automatique <span class="glyphicon glyphicon-plus"></span>', 'url' => 'automatic-email/create', 'class' => 'btn btn-primary']);

?>

<div class="accessory-index">
<?= GridView::widget([
'dataProvider' => $dataProvider,
'columns' => [
[
'attribute' => 'day',
'value' => function(AutomaticEmail $automaticEmail) {
return $automaticEmail->getDayAsString();
}
],
[
'attribute' => 'delay_before_distribution',
'value' => function(AutomaticEmail $automaticEmail) {
return $automaticEmail->getDelayBeforeDistributionAsString();
}
],
'subject',
[
'attribute' => 'message',
'format' => 'raw',
],
[
'attribute' => 'status',
'format' => 'raw',
'value' => function(AutomaticEmail $automaticEmail) {
return $automaticEmail->getStatusAsHtml();
}
],
[
'class' => 'yii\grid\ActionColumn',
'template' => '{update} {delete}',
'headerOptions' => ['class' => 'column-actions'],
'contentOptions' => ['class' => 'column-actions'],
'buttons' => [
'update' => function ($url, $model) {
return Html::a('<span class="glyphicon glyphicon-pencil"></span>', $url, [
'title' => 'Modifier', 'class' => 'btn btn-default'
]);
},
'delete' => function ($url, $model) {
return Html::a('<span class="glyphicon glyphicon-trash"></span>', $url, [
'title' => 'Supprimer', 'class' => 'btn btn-default'
]);
}
],
],
],
]); ?>
</div>

producer/views/order/create.php → backend/views/automatic-email/update.php View File

@@ -36,24 +36,17 @@ pris connaissance de la licence CeCILL, et que vous en avez accepté les
termes.
*/

$this->setTitle('Passer une commande') ;
use yii\helpers\Html;

$this->setTitle("Modifier un email automatique") ;
$this->addBreadcrumb(['label' => "Emails automatiques", 'url' => ['index']]) ;
$this->addBreadcrumb(['label' => Html::encode($automaticEmail->getSubject()), 'url' => ['update', 'id' => $automaticEmail->getId()]]) ;
$this->addBreadcrumb('Modifier') ;

?>
<div class="order-create">

<div class="automatic-email-update">
<?= $this->render('_form', [
'model' => $model,
'pointsSaleArray' => $pointsSaleArray,
'distributionDaysArray' => $distributionDaysArray,
'productsArray' => $productsArray,
'selectedProducts' => $selectedProducts,
'availableProducts' => $availableProducts,
'distribution' => $distribution,
'ordersArray' => $ordersArray,
'producersArray' => $producersArray,
'idProducer' => $idProducer,
'producer' => $producer,
'credit' => $credit
'automaticEmail' => $automaticEmail,
]) ?>

</div>

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

@@ -40,7 +40,7 @@ use yii\helpers\Html ;
use yii\widgets\ActiveForm;

$this->setTitle('Envoyer un email') ;
$this->addBreadcrumb(['label' => 'Communiquer', 'url' => ['user/index']]) ;
$this->addBreadcrumb(['label' => 'Communication', 'url' => ['user/index']]) ;
$this->addBreadcrumb($this->getTitle()) ;

?>

+ 3
- 1
backend/views/communicate/email.php View File

@@ -100,7 +100,9 @@ $this->render('@backend/views/user/_menu_filter.php',[
<br /><br />
<?php endif; ?>

<?= implode(', ', $usersArray); ?>
<?= implode(', ', array_map(function($user) {
return $user['email'];
}, $usersArray)); ?>
</div>
</div>
</div>

+ 2
- 2
backend/views/communicate/paper.php View File

@@ -38,8 +38,8 @@ termes.

use yii\helpers\Html ;

$this->setTitle('Communiquer par papier') ;
$this->addBreadcrumb('Communiquer') ;
$this->setTitle('Communication par papier') ;
$this->addBreadcrumb('Communication') ;

?>


+ 13
- 1
backend/views/dashboard-admin/index.php View File

@@ -66,10 +66,22 @@ $this->setMetaRefresh(true);
) ?>
</div>
<div class="col-lg-6 col-xs-6">
<?php
$colorBoxTicket = 'blue';
if($countTicketsAdminOpen == 0) {
$colorBoxTicket = 'green';
}
elseif($countTicketsAdminUnread == 0) {
$colorBoxTicket = 'blue';
}
elseif($countTicketsAdminUnread > 0) {
$colorBoxTicket = 'red';
}
?>
<?= AdminLTE::smallBox(
$countTicketsAdminOpen,
'Tickets',
$countTicketsAdminUnread ? 'red' : 'blue',
$colorBoxTicket,
'comments',
Yii::$app->urlManager->createUrl('support-admin/index')
) ?>

+ 6
- 5
backend/views/dashboard/index.php View File

@@ -37,6 +37,8 @@ termes.
*/

use domain\Distribution\Distribution\DistributionModule;
use domain\Feature\Feature\Feature;
use domain\Feature\Feature\FeatureModule;
use domain\Order\Order\Order;
use domain\Order\Order\OrderModule;
use domain\Producer\Producer\ProducerModule;
@@ -52,12 +54,13 @@ $subscriptionModule = SubscriptionModule::getInstance();
$producerModule = ProducerModule::getInstance();
$settingModule = SettingModule::getInstance();
$adminSettingBag = $settingModule->getAdminSettingBag();
$featureChecker = FeatureModule::getInstance()->getChecker();

$this->setTitle('Tableau de bord');

?>
<div class="dashboard-index">
<div <?php if($adminSettingBag->get('forumFlarumUrl')): ?>class="col-md-8"<?php endif; ?>>
<div <?php if($featureChecker->isEnabled(Feature::ALIAS_FORUM)): ?>class="col-md-8"<?php endif; ?>>
<?php if(Yii::$app->request->get('error_products_points_sale')): ?>
<div class="alert alert-warning">
Vous devez ajouter <?php if(!$productsCount): ?> des produits<?php endif; ?>
@@ -142,7 +145,7 @@ $this->setTitle('Tableau de bord');
<?php endif; ?>
</div>

<?php if($adminSettingBag->get('forumFlarumUrl')): ?>
<?php if($featureChecker->isEnabled(Feature::ALIAS_FORUM)): ?>
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-heading">
@@ -151,13 +154,11 @@ $this->setTitle('Tableau de bord');
</h3>
</div>
<div class="panel-body">
<?php //echo print_r($forumDiscussionsArray); ?>
<?php if($forumDiscussionsArray && count($forumDiscussionsArray['data'])): ?>
<table class="table">
<?php $forumDiscussionsLimitedArray = array_slice($forumDiscussionsArray['data'], 0, min(3, count($forumDiscussionsArray['data']))); ?>
<?php foreach($forumDiscussionsLimitedArray as $forumDiscussion): ?>
<tr>
<?php //echo print_r($forumDiscussion); ?>
<td><?= $forumDiscussion['attributes']['title']; ?></td>
<td><?= date('d/m à H:i', strtotime($forumDiscussion['attributes']['lastPostedAt'])); ?></td>
</tr>
@@ -167,7 +168,7 @@ $this->setTitle('Tableau de bord');
<p>Aucun sujet sur le forum.</p>
<?php endif; ?>
<p>
<a href="<?= $settingModule->getAdminSettingBag()->get('forumFlarumUrl'); ?>" class="btn btn-default">
<a href="<?= $settingModule->getAdminSettingBag()->get('forumFlarumUrl'); ?>" class="btn btn-default" target="_blank">
<span class="fa fa-comments"></span>
Consulter le forum
</a>

+ 26
- 14
backend/views/distribution/index.php View File

@@ -130,6 +130,7 @@ $this->setPageTitle('Distributions') ;
<td>Actif</td>
<td>Nom</td>
<td class="quantity-ordered">Commandé</td>
<td class="quantity-remaining">Reste</td>
<td class="quantity-max">Maximum</td>
</tr>
</thead>
@@ -143,17 +144,26 @@ $this->setPageTitle('Distributions') ;
</td>
<td>{{ product.name }}</td>
<td class="quantity-ordered">
<span v-if="isProductMaximumQuantityExceeded(product)" class="glyphicon glyphicon-alert"></span>
{{ product.quantity_ordered ? product.quantity_ordered + ' '+ ((product.unit == 'piece') ? ' p.' : ' '+(product.unit == 'g' || product.unit == 'kg') ? 'kg' : 'litre(s)') : '&empty;' }}
</td>

<td class="quantity-remaining">
<span class="infinite" v-if="(getProductQuantityRemainingGlobal(product) === null)">&infin;</span>
<span class="negative" v-else-if="getProductQuantityRemainingGlobal(product) <= 0">
{{ getProductQuantityRemainingGlobal(product) }} {{ product.unit == 'piece' ? ' p.' : ' '+(product.unit == 'g' || product.unit == 'kg') ? 'kg' : 'litre(s)' }}
<span class="glyphicon glyphicon-alert" v-if="getProductQuantityRemainingGlobal(product) < 0"></span>
</span>
<span class="has-quantity" v-else>{{ getProductQuantityRemainingGlobal(product) }} {{ product.unit == 'piece' ? ' p.' : ' '+(product.unit == 'g' || product.unit == 'kg') ? 'kg' : 'litre(s)' }}</span>
</td>

<td class="quantity-max">
<div class="input-group">
<input type="text" class="form-control quantity-max" placeholder="&infin;" :data-id-product="product.id" v-model="getProductDistribution(product).quantity_max" @keyup="productQuantityMaxChange" />
<span class="input-group-addon">{{ (product.unit == 'piece') ? 'p.' : ' '+((product.unit == 'g' || product.unit == 'kg') ? 'kg' : 'litre(s)') }}</span>
<span class="input-group-addon">{{ labelUnitReference(product.unit) }}</span>
</div>
<div v-if=" false && getProductDistribution(product).quantity_max != product.quantity_max">
Limitation accessoires : <br />
{{ product.quantity_max }}
<div class="limit-quantity-accessories" v-if="!showLoading && product.quantity_max && (getProductDistribution(product).quantity_max > product.quantity_max || !getProductDistribution(product).quantity_max)">
<span class="glyphicon glyphicon-warning-sign"></span> Limitation accessoires :
<strong>{{ product.quantity_max }} {{ labelUnitReference(product.unit) }}</strong>
</div>
</td>
</tr>
@@ -272,7 +282,7 @@ $this->setPageTitle('Distributions') ;
:units="units"
@close="closeModalOrderForm(true)"
@ordercreatedupdated="orderCreatedUpdated"
@updateproductorderprices="updateProductOrderPrices"
@updateproductorders="updateProductOrders"
></order-form>

<div id="wrapper-nav-points-sale">
@@ -535,7 +545,7 @@ $this->setPageTitle('Distributions') ;
:units="units"
@close="closeModalOrderForm(false)"
@ordercreatedupdated="orderCreatedUpdated"
@updateproductorderprices="updateProductOrderPrices"
@updateproductorders="updateProductOrders"
@updateinvoiceprices="updateInvoicePrices"
></order-form>

@@ -709,7 +719,7 @@ $this->setPageTitle('Distributions') ;
<tbody>
<tr v-for="product in products" v-if="product.status >= 0 || order.productOrder[product.id].quantity > 0" :class="(order.productOrder[product.id].quantity > 0) ? 'product-ordered' : ''">
<td>
<span class="label label-success" v-if="loadingUpdateProductOrder || order.productOrder[product.id].active">Actif</span>
<span class="label label-success" v-if="order.productOrder[product.id].active">Actif</span>
<span class="label label-danger" v-else>Inactif</span>
</td>
<td>
@@ -745,12 +755,14 @@ $this->setPageTitle('Distributions') ;
</span>
</div>
</td>
<td class="quantity-remaining infinite" v-if="(getProductQuantityRemaining(product) === null) || order.productOrder[product.id].unit != product.unit">&infin;</td>
<td class="quantity-remaining negative" v-else-if="getProductQuantityRemaining(product) <= 0">
{{ getProductQuantityRemaining(product) }} {{ order.productOrder[product.id].unit == 'piece' ? ' p.' : ' '+(order.productOrder[product.id].unit == 'g' || order.productOrder[product.id].unit == 'kg') ? 'kg' : 'litre(s)' }}
<span class="glyphicon glyphicon-alert" v-if="getProductQuantityRemaining(product) < 0"></span>
<td class="quantity-remaining">
<span class="infinite" v-if="(getProductQuantityRemaining(order, product) === null) || order.productOrder[product.id].unit != product.unit">&infin;</span>
<span class="negative" v-else-if="getProductQuantityRemaining(order, product) <= 0">
{{ getProductQuantityRemaining(order, product) }} {{ labelUnitReference(order.productOrder[product.id].unit) }}
<span class="glyphicon glyphicon-alert" v-if="getProductQuantityRemaining(order, product) < 0"></span>
</span>
<span class="has-quantity" v-else>{{ getProductQuantityRemaining(order, product) }} {{ labelUnitReference(order.productOrder[product.id].unit) }}</span>
</td>
<td class="quantity-remaining has-quantity" v-else>{{ getProductQuantityRemaining(product) }} {{ order.productOrder[product.id].unit == 'piece' ? ' p.' : ' '+(order.productOrder[product.id].unit == 'g' || order.productOrder[product.id].unit == 'kg') ? 'kg' : 'litre(s)' }}</td>
</tr>
</tbody>
</table>
@@ -764,7 +776,7 @@ $this->setPageTitle('Distributions') ;
<button class="modal-default-button btn btn-primary" @click="submitFormCreate" v-if="!order.id">Créer</button>

<div class="right">
<button class="modal-default-button btn btn-info btn-update-prices" @click="updateProductOrderPrices(true)">Recharger les prix</button>
<button class="modal-default-button btn btn-info btn-update-prices" @click="updateProductOrders(true)">Recharger les prix</button>
<button v-if="order.id" class="modal-default-button btn btn-info btn-update-prices" @click="updateInvoicePrices(true)">
Réinitialiser les prix facturés
</button>

+ 2
- 2
backend/views/invoice/index.php View File

@@ -117,7 +117,7 @@ $this->addButton(['label' => 'Nouvelle facture <span class="glyphicon glyphicon-
}
],
[
'attribute' => 'paid',
'attribute' => 'is_paid',
'label' => 'Payée',
'filter' => [
0 => 'Non',
@@ -127,7 +127,7 @@ $this->addButton(['label' => 'Nouvelle facture <span class="glyphicon glyphicon-
'headerOptions' => ['class' => 'column-hide-on-mobile'],
'filterOptions' => ['class' => 'column-hide-on-mobile'],
'contentOptions' => ['class' => 'column-hide-on-mobile'],
'value' => function ($invoice) use ( $invoiceModule) {
'value' => function ($invoice) use ($invoiceModule) {
if($invoiceModule->isInvoicePaid($invoice)) {
return '<span class="label label-success">Oui</span>';
}

+ 6
- 1
backend/views/layouts/content.php View File

@@ -44,6 +44,8 @@ use common\helpers\GlobalParam;
$producerModule = $this->getProducerModule();
$adminSettingBag = $this->getSettingModule()->getAdminSettingBag();
$sharedPointSaleModule = $this->getSharedPointSaleModule();
$userModule = $this->getUserModule();
$userCurrent = GlobalParam::getCurrentUser();
$producer = GlobalParam::getCurrentProducer();

?>
@@ -93,7 +95,10 @@ $producer = GlobalParam::getCurrentProducer();
]) ?>

<?php $producer = GlobalParam::getCurrentProducer(); ?>
<?php if($producer && !$producerModule->isUpToDateWithOpendistribVersion($producer) && $producer->option_display_message_new_opendistrib_version): ?>
<?php if($producer
&& !$producerModule->isUpToDateWithOpendistribVersion($producer)
&& $producer->option_display_message_new_opendistrib_version
&& !$userModule->getAuthorizationChecker()->isGrantedAsAdministrator($userCurrent)): ?>
<div class="alert alert-success">
<p>
<i class="icon fa fa-cogs"></i>

+ 14
- 1
backend/views/layouts/header.php View File

@@ -40,8 +40,11 @@ use common\helpers\GlobalParam;
use common\helpers\Image;
use common\helpers\Price;
use domain\Distribution\Distribution\Distribution;
use domain\Feature\Feature\Feature;
use domain\Feature\Feature\FeatureModule;
use domain\Producer\Producer\Producer;
use domain\Producer\Producer\ProducerModule;
use domain\Setting\AdminSettingBag;
use domain\User\User\UserModule;
use common\helpers\Environment;
use yii\helpers\Html;
@@ -50,6 +53,8 @@ $userModule = UserModule::getInstance();
$producerModule = ProducerModule::getInstance();
$producer = GlobalParam::getCurrentProducer();
$userCurrent = GlobalParam::getCurrentUser();
$featureChecker = FeatureModule::getInstance()->getChecker();
$adminSettingBag = AdminSettingBag::getInstance();

?>

@@ -309,7 +314,6 @@ $userCurrent = GlobalParam::getCurrentUser();
</a>
</li>


<?php if ($userModule->getAuthorizationChecker()->isGrantedAsProducer($userCurrent)): ?>
<li>
<a href="<?= Yii::$app->urlManagerProducer->createAbsoluteUrl(['site/index', 'slug_producer' => GlobalParam::getCurrentProducer()->slug]); ?>">
@@ -319,6 +323,15 @@ $userCurrent = GlobalParam::getCurrentUser();
</li>
<?php endif; ?>

<?php if($featureChecker->isEnabled(Feature::ALIAS_FORUM)): ?>
<li>
<a href="<?= $adminSettingBag->get('forumFlarumUrl') ?>" target="_blank">
<i class="bi bi-people"></i>
<span class="hidden-xs hidden-sm">Forum</span>
</a>
</li>
<?php endif; ?>

<li class="dropdown user user-menu">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class="bi bi-person"></i>

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

@@ -245,12 +245,13 @@ $isUserCurrentGrantedAsProducer = $userModule->getAuthorizationChecker()->isGran
],
['label' => 'Abonnements', 'icon' => 'repeat', 'url' => ['/subscription/index'], 'visible' => $isUserCurrentGrantedAsProducer, 'active' => Yii::$app->controller->id == 'subscription'],
[
'label' => 'Communiquer',
'label' => 'Communication',
'icon' => 'bullhorn',
'url' => ['/communicate/email'],
'visible' => $isUserCurrentGrantedAsProducer,
'items' => [
['label' => 'Email', 'icon' => 'paper-plane', 'url' => ['/communicate/email'], 'visible' => $isUserCurrentGrantedAsProducer],
['label' => 'Email automatique', 'icon' => 'paper-plane-o', 'url' => ['/automatic-email/index'], 'visible' => $isUserCurrentGrantedAsProducer && $featureChecker->isEnabled(Feature::ALIAS_AUTOMATIC_EMAIL)],
['label' => 'Papier', 'icon' => 'print', 'url' => ['/communicate/paper'], 'visible' => $isUserCurrentGrantedAsProducer],
]
],
@@ -272,6 +273,7 @@ $isUserCurrentGrantedAsProducer = $userModule->getAuthorizationChecker()->isGran
['label' => 'Chiffre d\'affaire', 'icon' => 'line-chart', 'url' => ['/stats/index'], 'visible' => $isUserCurrentGrantedAsProducer],
['label' => 'Rapports', 'icon' => 'pencil-square-o', 'url' => ['/report/index'], 'visible' => $isUserCurrentGrantedAsProducer],
['label' => 'Produits', 'icon' => 'table', 'url' => ['/stats/products'], 'visible' => $isUserCurrentGrantedAsProducer],
['label' => 'Export paiements', 'icon' => 'euro', 'url' => ['/report/payments'], 'visible' => $isUserCurrentGrantedAsProducer],
],
],
['label' => 'Paramètres', 'icon' => 'cog', 'url' => ['/producer/update'], 'visible' => $isUserCurrentGrantedAsProducer],

+ 1
- 3
backend/views/producer-admin/index.php View File

@@ -159,9 +159,7 @@ $this->addButton(['label' => 'Nouveau producteur <span class="glyphicon glyphico

if($isBilled && $producerModule->getSolver()->isFreeBillingPeriodSponsorship($producer)) {
$str .= "<br /><p><span class=\"glyphicon glyphicon-warning-sign\"></span> <strong>Pas de facturation, offre de parrainage jusqu'au ".$producerModule->getSolver()->getDateEndFreeBillingPeriodSponsorship($producer)->format('d/m/Y')."</strong></p>";
if(!$producer->getSponsorshipSponsorReward()) {
$str .= '<p><i class="fa fa-gift"></i> Créer un avoir de 30€ pour le parrain <strong>'.Html::encode($producer->getSponsoredBy()->getName()).'</strong> sur Dolibarr et valider la récompense parrain.</p>';
}
$str .= '<p><i class="fa fa-gift"></i> Créer un avoir pour le parrain <strong>'.Html::encode($producer->getSponsoredBy()->getName()).'</strong> sur Dolibarr.</p>';
}

return $str;

+ 70
- 0
backend/views/report/payments.php View File

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

/**
Copyright Souke (2018)

contact@souke.fr

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

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

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

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

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

use yii\helpers\Html;
use yii\widgets\ActiveForm;

$this->setTitle('Paiements') ;
$this->addBreadcrumb('Paiements') ;

?>

<div class="row">
<div class="col-md-8">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
<i class="fa fa-download"></i>
Export CSV des paiements
</h3>
</div>
<div class="panel-body">
<?php $form = ActiveForm::begin(); ?>
<?= $form->field($reportPaymentsForm, 'date_start')->textInput([
'class' => 'datepicker form-control'
]) ?>
<?= $form->field($reportPaymentsForm, 'date_end')->textInput([
'class' => 'datepicker form-control'
]) ?>
<?= Html::submitButton('Exporter', ['class' => 'btn btn-primary']) ?>
<?php ActiveForm::end(); ?>

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

+ 2
- 2
backend/views/sponsorship/index.php View File

@@ -90,8 +90,8 @@ $sponsorshipLink = $producerModule->getSolver()->getSponsorshipLink($producer);
Votre filleul démarre sur le logiciel avec <strong>3 mois offerts</strong>.
</li>
<li>
Vous obtenez <strong>30€ de réduction</strong> sur vos prochaines factures dès lors que votre filleul
utilise le logiciel en production.
Pendant les 3 mois offerts de votre filleul, vous obtenez chaque début de mois <strong>une réduction</strong>
équivalente à <a href="<?= Yii::$app->urlManager->createUrl(['producer/billing']) ?>">la tranche dans laquelle il se situe</a>.
</li>
</ul>
</div>

+ 1
- 1
backend/views/support/index.php View File

@@ -120,7 +120,7 @@ $supportOnline = $adminSettingBag->get('supportOnline');
<div class="info-box">
<span class="info-box-icon bg-yellow"><i class="fa fa-users"></i></span>
<div class="info-box-content">
<span class="info-box-text"><br/><?= Html::a("Ouvrir une discussion sur le forum", $adminSettingBag->get('forumFlarumUrl'), ['class' => 'btn btn-sm btn-default', 'target' => '_blank']); ?></span>
<span class="info-box-text"><br/><?= Html::a("Aller sur le forum", $adminSettingBag->get('forumFlarumUrl'), ['class' => 'btn btn-sm btn-default', 'target' => '_blank']); ?></span>
</div>
</div>
</div>

+ 11
- 0
backend/views/user-admin/email_deliverability.php View File

@@ -104,6 +104,17 @@ $this->addBreadcrumb($this->getTitle());
return $html;
}
],
[
'attribute' => 'note_emails',
'format' => 'raw',
'value' => function($user) {
if($user->note_emails) {
return nl2br($user->note_emails);
}

return '';
}
],
[
'attribute' => 'producers',
'label' => 'Profils',

+ 29
- 12
backend/views/user/_form.php View File

@@ -111,18 +111,35 @@ $distributionModule = DistributionModule::getInstance();
</h3>
</div>
<div class="panel-body">
<?= $form->field($model, 'points_sale')->checkboxlist(
ArrayHelper::map($pointsSaleArray, 'id', function ($pointSale) use ($model) {
$commentUserPointSale = isset($pointSale->userPointSale[0]) ? $pointSale->userPointSale[0]->comment : '';
$html = Html::encode($pointSale->name);
if ($pointSale->restricted_access) {
$html .= '<input type="text" placeholder="Commentaire" class="form-control" name="User[comment_point_sale_' . $pointSale->id . ']" value="' . (($model->id) ? Html::encode($commentUserPointSale) : '') . '" />';
}
return $html;
}), [
'encode' => false
]);
?>
<div class="row">
<div class="col-md-8">
<?= $form->field($model, 'points_sale')->checkboxlist(
ArrayHelper::map($pointsSaleArray, 'id', function ($pointSale) use ($model) {
$commentUserPointSale = isset($pointSale->userPointSale[0]) ? $pointSale->userPointSale[0]->comment : '';
$html = Html::encode($pointSale->name);
if ($pointSale->restricted_access) {
$html .= '<input type="text" placeholder="Commentaire" class="form-control" name="User[comment_point_sale_' . $pointSale->id . ']" value="' . (($model->id) ? Html::encode($commentUserPointSale) : '') . '" />';
}
return $html;
}), [
'encode' => false
]);
?>
</div>
<div class="col-md-4">
<?= $form->field($model, 'exclusive_access_selected_points_sale')->widget(Toggle::class,
[
'options' => [
'data-id' => $model->id,
'data-on' => 'Oui',
'data-off' => 'Non',
'data-onstyle' => 'success',
'data-offstyle' => 'default',
],
]
); ?>
</div>
</div>
</div>
</div>
<?php endif; ?>

+ 3
- 1
backend/views/user/credit.php View File

@@ -114,7 +114,9 @@ $this->addBreadcrumb('Cagnotte') ;
</h3>
</div>
<div class="panel-body">
<?php $form = ActiveForm::begin(); ?>
<?php $form = ActiveForm::begin([
'enableClientValidation' => false
]); ?>
<div class="row">
<div class="col-md-6">
<?= $form->field($creditForm, 'type')->dropDownList([

+ 112
- 93
backend/web/css/screen.css View File

@@ -2400,111 +2400,136 @@ termes.
.distribution-index #calendar .vc-day.is-not-in-month .vc-highlights * {
opacity: 1 !important;
}
/* line 97, ../sass/distribution/_index.scss */
/* line 96, ../sass/distribution/_index.scss */
.distribution-index table td.quantity-remaining {
text-align: right;
}
/* line 99, ../sass/distribution/_index.scss */
.distribution-index table td.quantity-remaining .has-quantity, .distribution-index table td.quantity-remaining .infinite {
color: #00A65A;
}
/* line 103, ../sass/distribution/_index.scss */
.distribution-index table td.quantity-remaining .negative {
color: #DD4B39;
}
/* line 107, ../sass/distribution/_index.scss */
.distribution-index table td.quantity-remaining .infinite, .distribution-index table td.quantity-remaining .empty {
font-size: 18px;
}
/* line 113, ../sass/distribution/_index.scss */
.distribution-index #products td.quantities {
width: 100px;
text-align: right;
}
/* line 102, ../sass/distribution/_index.scss */
/* line 118, ../sass/distribution/_index.scss */
.distribution-index #products input.quantity-max {
width: 50px;
text-align: center;
display: inline;
}
/* line 110, ../sass/distribution/_index.scss */
/* line 126, ../sass/distribution/_index.scss */
.distribution-index #infos-top .col-md-4 {
padding: 0px;
}
/* line 116, ../sass/distribution/_index.scss */
/* line 132, ../sass/distribution/_index.scss */
.distribution-index #infos-top .info-box {
min-height: 96px;
height: 96px;
}
/* line 120, ../sass/distribution/_index.scss */
/* line 136, ../sass/distribution/_index.scss */
.distribution-index #infos-top .info-box .info-box-icon {
height: 96px;
width: 50px;
line-height: 96px;
}
/* line 125, ../sass/distribution/_index.scss */
/* line 141, ../sass/distribution/_index.scss */
.distribution-index #infos-top .info-box .info-box-icon i.fa {
font-size: 30px;
}
/* line 130, ../sass/distribution/_index.scss */
/* line 146, ../sass/distribution/_index.scss */
.distribution-index #infos-top .info-box .info-box-content {
margin-left: 55px;
}
/* line 133, ../sass/distribution/_index.scss */
/* line 149, ../sass/distribution/_index.scss */
.distribution-index #infos-top .info-box .info-box-content .info-box-text {
font-size: 12px;
}
/* line 136, ../sass/distribution/_index.scss */
/* line 152, ../sass/distribution/_index.scss */
.distribution-index #infos-top .info-box .info-box-content .info-box-text .btn {
font-size: 12px;
text-transform: uppercase;
}
/* line 142, ../sass/distribution/_index.scss */
/* line 158, ../sass/distribution/_index.scss */
.distribution-index #infos-top .info-box .info-box-content .info-box-number {
font-size: 14px;
}
/* line 149, ../sass/distribution/_index.scss */
/* line 165, ../sass/distribution/_index.scss */
.distribution-index #infos-top #info-box-distribution .btn-active-week {
float: right;
}
/* line 155, ../sass/distribution/_index.scss */
/* line 171, ../sass/distribution/_index.scss */
.distribution-index #infos-top #summary-ca-weight .normal {
font-weight: normal;
}
/* line 165, ../sass/distribution/_index.scss */
/* line 181, ../sass/distribution/_index.scss */
.distribution-index #modal-products table.table thead tr td {
font-weight: bold;
}
/* line 171, ../sass/distribution/_index.scss */
/* line 187, ../sass/distribution/_index.scss */
.distribution-index #modal-products table.table td.quantity-ordered,
.distribution-index #modal-products table.table td.quantity-max {
text-align: center;
}
/* line 176, ../sass/distribution/_index.scss */
/* line 192, ../sass/distribution/_index.scss */
.distribution-index #modal-products table.table td.quantity-ordered {
width: 50px;
}
/* line 180, ../sass/distribution/_index.scss */
/* line 196, ../sass/distribution/_index.scss */
.distribution-index #modal-products table.table td.quantity-max {
width: 120px;
}
/* line 183, ../sass/distribution/_index.scss */
/* line 199, ../sass/distribution/_index.scss */
.distribution-index #modal-products table.table td.quantity-max input {
text-align: center;
min-width: 50px;
}
/* line 191, ../sass/distribution/_index.scss */
/* line 204, ../sass/distribution/_index.scss */
.distribution-index #modal-products table.table td.quantity-max .limit-quantity-accessories {
margin-top: 7px;
font-size: 12px;
}
/* line 208, ../sass/distribution/_index.scss */
.distribution-index #modal-products table.table td.quantity-max .limit-quantity-accessories .quantity {
font-weight: bold;
}
/* line 216, ../sass/distribution/_index.scss */
.distribution-index #orders {
position: relative;
}
/* line 196, ../sass/distribution/_index.scss */
/* line 221, ../sass/distribution/_index.scss */
.distribution-index #orders .panel-heading .buttons .btn {
position: relative;
top: -19px;
float: right;
margin-left: 10px;
}
/* line 205, ../sass/distribution/_index.scss */
/* line 230, ../sass/distribution/_index.scss */
.distribution-index #orders #wrapper-nav-points-sale {
margin-bottom: 10px;
}
/* line 208, ../sass/distribution/_index.scss */
/* line 233, ../sass/distribution/_index.scss */
.distribution-index #orders #wrapper-nav-points-sale ul#nav-points-sale {
margin: 0px;
padding: 0px;
list-style-type: none;
}
/* line 213, ../sass/distribution/_index.scss */
/* line 238, ../sass/distribution/_index.scss */
.distribution-index #orders #wrapper-nav-points-sale ul#nav-points-sale li {
float: left;
margin-right: 10px;
margin-bottom: 10px;
}
/* line 219, ../sass/distribution/_index.scss */
/* line 244, ../sass/distribution/_index.scss */
.distribution-index #orders #wrapper-nav-points-sale ul#nav-points-sale li a .label {
background-color: white;
border: solid 1px #e0e0e0;
@@ -2512,7 +2537,7 @@ termes.
-webkit-border-radius: 10px;
border-radius: 10px;
}
/* line 230, ../sass/distribution/_index.scss */
/* line 255, ../sass/distribution/_index.scss */
.distribution-index #orders #buttons-top-orders {
background-color: #F5F5F5;
padding: 10px 20px;
@@ -2522,15 +2547,15 @@ termes.
border-radius: 5px;
margin-bottom: 20px;
}
/* line 241, ../sass/distribution/_index.scss */
/* line 266, ../sass/distribution/_index.scss */
.distribution-index #orders #buttons-top-orders .right {
float: right;
}
/* line 245, ../sass/distribution/_index.scss */
/* line 270, ../sass/distribution/_index.scss */
.distribution-index #orders #buttons-top-orders .dropdown {
display: inline-block;
}
/* line 250, ../sass/distribution/_index.scss */
/* line 275, ../sass/distribution/_index.scss */
.distribution-index #orders .point-sale-totals {
background-color: white;
padding: 10px 20px;
@@ -2540,30 +2565,30 @@ termes.
border-radius: 5px;
margin-bottom: 20px;
}
/* line 257, ../sass/distribution/_index.scss */
/* line 282, ../sass/distribution/_index.scss */
.distribution-index #orders .point-sale-totals .title {
color: gray;
font-size: 13px;
margin-right: 13px;
text-transform: uppercase;
}
/* line 267, ../sass/distribution/_index.scss */
/* line 292, ../sass/distribution/_index.scss */
.distribution-index #orders table td.column-user {
position: relative;
}
/* line 270, ../sass/distribution/_index.scss */
/* line 295, ../sass/distribution/_index.scss */
.distribution-index #orders table td.column-user:hover .shortcuts {
display: block;
}
/* line 274, ../sass/distribution/_index.scss */
/* line 299, ../sass/distribution/_index.scss */
.distribution-index #orders table td.column-user a {
color: #333;
}
/* line 277, ../sass/distribution/_index.scss */
/* line 302, ../sass/distribution/_index.scss */
.distribution-index #orders table td.column-user a:hover {
text-decoration: underline;
}
/* line 282, ../sass/distribution/_index.scss */
/* line 307, ../sass/distribution/_index.scss */
.distribution-index #orders table td.column-user .shortcuts {
display: none;
float: right;
@@ -2571,19 +2596,19 @@ termes.
top: 1px;
right: 1px;
}
/* line 290, ../sass/distribution/_index.scss */
/* line 315, ../sass/distribution/_index.scss */
.distribution-index #orders table td.column-user .user-trust-alert {
color: red;
}
/* line 295, ../sass/distribution/_index.scss */
/* line 320, ../sass/distribution/_index.scss */
.distribution-index #orders table td.column-delivery-note {
position: relative;
}
/* line 299, ../sass/distribution/_index.scss */
/* line 324, ../sass/distribution/_index.scss */
.distribution-index #orders table td.tiller {
width: 60px;
}
/* line 302, ../sass/distribution/_index.scss */
/* line 327, ../sass/distribution/_index.scss */
.distribution-index #orders table td.tiller label {
font-size: 12px;
cursor: pointer;
@@ -2591,88 +2616,88 @@ termes.
top: -2px;
font-weight: normal;
}
/* line 311, ../sass/distribution/_index.scss */
/* line 336, ../sass/distribution/_index.scss */
.distribution-index #orders table td.column-actions {
position: relative;
text-align: right;
}
/* line 315, ../sass/distribution/_index.scss */
/* line 340, ../sass/distribution/_index.scss */
.distribution-index #orders table td.column-actions .wrapper-button-dropdown {
display: inline-block;
}
/* line 319, ../sass/distribution/_index.scss */
/* line 344, ../sass/distribution/_index.scss */
.distribution-index #orders table td.column-actions .dropdown-menu {
left: -70px;
width: 227px;
}
/* line 324, ../sass/distribution/_index.scss */
/* line 349, ../sass/distribution/_index.scss */
.distribution-index #orders table td.column-actions .modal-form-order,
.distribution-index #orders table td.column-actions .modal-payment {
text-align: left;
}
/* line 329, ../sass/distribution/_index.scss */
/* line 354, ../sass/distribution/_index.scss */
.distribution-index #orders table td.column-actions .add-subscription {
position: relative;
}
/* line 332, ../sass/distribution/_index.scss */
/* line 357, ../sass/distribution/_index.scss */
.distribution-index #orders table td.column-actions .add-subscription .glyphicon-plus {
position: absolute;
top: 4px;
right: 4px;
font-size: 7px;
}
/* line 341, ../sass/distribution/_index.scss */
/* line 366, ../sass/distribution/_index.scss */
.distribution-index #orders table td.column-state-payment {
width: 133px;
}
/* line 347, ../sass/distribution/_index.scss */
/* line 372, ../sass/distribution/_index.scss */
.distribution-index #orders table td.column-credit a.positive {
color: green;
}
/* line 350, ../sass/distribution/_index.scss */
/* line 375, ../sass/distribution/_index.scss */
.distribution-index #orders table td.column-credit a.negative {
color: red;
}
/* line 356, ../sass/distribution/_index.scss */
/* line 381, ../sass/distribution/_index.scss */
.distribution-index #orders table .state-payment-mobile {
display: none;
}
/* line 360, ../sass/distribution/_index.scss */
/* line 385, ../sass/distribution/_index.scss */
.distribution-index #orders table td.column-payment {
position: relative;
}
/* line 363, ../sass/distribution/_index.scss */
/* line 388, ../sass/distribution/_index.scss */
.distribution-index #orders table td.column-payment div.btn-group {
width: 125px;
}
/* line 369, ../sass/distribution/_index.scss */
/* line 394, ../sass/distribution/_index.scss */
.distribution-index #orders table tr.view ul {
list-style-type: none;
margin-left: 0px;
padding-left: 15px;
}
/* line 379, ../sass/distribution/_index.scss */
/* line 404, ../sass/distribution/_index.scss */
.distribution-index #orders table tr.view .comment {
margin-top: 20px;
}
/* line 383, ../sass/distribution/_index.scss */
/* line 408, ../sass/distribution/_index.scss */
.distribution-index #orders table tr.view .delivery {
margin-top: 20px;
}
/* line 392, ../sass/distribution/_index.scss */
/* line 417, ../sass/distribution/_index.scss */
.distribution-index .modal-form-order .modal-container {
width: 100%;
padding: 0px;
}
/* line 396, ../sass/distribution/_index.scss */
/* line 421, ../sass/distribution/_index.scss */
.distribution-index .modal-form-order .modal-container .modal-body {
padding-right: 15px;
}
/* line 399, ../sass/distribution/_index.scss */
/* line 424, ../sass/distribution/_index.scss */
.distribution-index .modal-form-order .modal-container .modal-body table {
margin-bottom: 150px;
}
/* line 404, ../sass/distribution/_index.scss */
/* line 429, ../sass/distribution/_index.scss */
.distribution-index .modal-form-order .modal-container .modal-footer {
border-top-color: #f4f4f4;
position: fixed;
@@ -2684,64 +2709,64 @@ termes.
text-align: center;
border-top: solid 1px #e0e0e0;
}
/* line 416, ../sass/distribution/_index.scss */
/* line 441, ../sass/distribution/_index.scss */
.distribution-index .modal-form-order .modal-container .modal-footer .actions-form button {
float: none;
}
/* line 420, ../sass/distribution/_index.scss */
/* line 445, ../sass/distribution/_index.scss */
.distribution-index .modal-form-order .modal-container .modal-footer .actions-form div.right {
float: right;
}
/* line 427, ../sass/distribution/_index.scss */
/* line 452, ../sass/distribution/_index.scss */
.distribution-index .modal-form-order .btn-credit {
float: right;
}
/* line 433, ../sass/distribution/_index.scss */
/* line 458, ../sass/distribution/_index.scss */
.distribution-index .modal-form-order table.table-products .product-ordered td {
background-color: #e9e9e9;
}
/* line 437, ../sass/distribution/_index.scss */
/* line 462, ../sass/distribution/_index.scss */
.distribution-index .modal-form-order table.table-products .product-ordered input.input-quantity {
font-size: 16px;
font-weight: bold;
}
/* line 443, ../sass/distribution/_index.scss */
/* line 468, ../sass/distribution/_index.scss */
.distribution-index .modal-form-order table.table-products td.price {
width: 150px;
}
/* line 446, ../sass/distribution/_index.scss */
/* line 471, ../sass/distribution/_index.scss */
.distribution-index .modal-form-order table.table-products td.price input {
text-align: center;
}
/* line 450, ../sass/distribution/_index.scss */
/* line 475, ../sass/distribution/_index.scss */
.distribution-index .modal-form-order table.table-products td.price .input-group-addon {
background-color: #eee;
}
/* line 454, ../sass/distribution/_index.scss */
/* line 479, ../sass/distribution/_index.scss */
.distribution-index .modal-form-order table.table-products td.price .invoice-price {
margin-top: 8px;
}
/* line 456, ../sass/distribution/_index.scss */
/* line 481, ../sass/distribution/_index.scss */
.distribution-index .modal-form-order table.table-products td.price .invoice-price .label-invoice-price {
font-size: 11px;
font-weight: bold;
color: gray;
}
/* line 464, ../sass/distribution/_index.scss */
/* line 489, ../sass/distribution/_index.scss */
.distribution-index .modal-form-order table.table-products td.quantity {
width: 165px;
}
/* line 467, ../sass/distribution/_index.scss */
/* line 492, ../sass/distribution/_index.scss */
.distribution-index .modal-form-order table.table-products td.quantity input {
text-align: center;
color: black;
}
/* line 472, ../sass/distribution/_index.scss */
/* line 497, ../sass/distribution/_index.scss */
.distribution-index .modal-form-order table.table-products td.quantity .form-control {
border-right: 0px none;
padding-right: 4px;
}
/* line 477, ../sass/distribution/_index.scss */
/* line 502, ../sass/distribution/_index.scss */
.distribution-index .modal-form-order table.table-products td.quantity .input-group-addon {
padding: 5px;
padding-left: 0px;
@@ -2749,35 +2774,19 @@ termes.
border-left: 0px none;
border-right: 0px none;
}
/* line 486, ../sass/distribution/_index.scss */
.distribution-index .modal-form-order table.table-products td.quantity-remaining {
text-align: right;
}
/* line 489, ../sass/distribution/_index.scss */
.distribution-index .modal-form-order table.table-products td.quantity-remaining.quantity-remaining, .distribution-index .modal-form-order table.table-products td.quantity-remaining.infinite {
color: #00A65A;
}
/* line 493, ../sass/distribution/_index.scss */
.distribution-index .modal-form-order table.table-products td.quantity-remaining.negative {
color: #DD4B39;
}
/* line 497, ../sass/distribution/_index.scss */
.distribution-index .modal-form-order table.table-products td.quantity-remaining.infinite, .distribution-index .modal-form-order table.table-products td.quantity-remaining.empty {
font-size: 18px;
}
/* line 504, ../sass/distribution/_index.scss */
/* line 513, ../sass/distribution/_index.scss */
.distribution-index .modal-form-order .actions-form button {
margin-left: 15px;
}
/* line 512, ../sass/distribution/_index.scss */
/* line 521, ../sass/distribution/_index.scss */
.distribution-index .modal-payment .info-box .info-box-icon {
width: 50px;
}
/* line 515, ../sass/distribution/_index.scss */
/* line 524, ../sass/distribution/_index.scss */
.distribution-index .modal-payment .info-box .info-box-icon i {
font-size: 30px;
}
/* line 520, ../sass/distribution/_index.scss */
/* line 529, ../sass/distribution/_index.scss */
.distribution-index .modal-payment .info-box .info-box-content {
margin-left: 50px;
}
@@ -2802,12 +2811,17 @@ termes.
.user-update .panel-point-sales label.control-label {
display: none;
}
/* line 18, ../sass/user/_form.scss */
/* line 16, ../sass/user/_form.scss */
.user-create .panel-point-sales .field-user-exclusive_access_selected_points_sale label.control-label,
.user-update .panel-point-sales .field-user-exclusive_access_selected_points_sale label.control-label {
display: block;
}
/* line 22, ../sass/user/_form.scss */
.user-create .panel-user-groups label.control-label,
.user-update .panel-user-groups label.control-label {
display: none;
}
/* line 25, ../sass/user/_form.scss */
/* line 29, ../sass/user/_form.scss */
.user-create #user-points_sale label,
.user-create #user-user_groups label,
.user-update #user-points_sale label,
@@ -2819,7 +2833,7 @@ termes.
padding: 5px 10px;
border: solid 1px #e0e0e0;
}
/* line 33, ../sass/user/_form.scss */
/* line 37, ../sass/user/_form.scss */
.user-create #user-points_sale label .form-control,
.user-create #user-user_groups label .form-control,
.user-update #user-points_sale label .form-control,
@@ -3321,6 +3335,11 @@ termes.
margin-top: 0px;
}

/* line 4, ../sass/automatic-email/_form.scss */
.automatic-email-form form label {
display: block;
}

/**
Copyright Souke (2018)


+ 20
- 5
backend/web/js/backend.js View File

@@ -37,7 +37,6 @@
$(document).ready(function () {
opendistrib_datepicker();
opendistrib_popover();
opendistrib_commandeauto();
opendistrib_points_vente_acces();
opendistrib_tooltip();
opendistrib_ordre_produits();
@@ -54,6 +53,7 @@ $(document).ready(function () {
opendistrib_gridview_pagesize();
opendistrib_producers_admin();
opendistrib_user_form();
opendistrib_user_credit();
opendistrib_features_index();
opendistrib_point_sale_form();
opendistrib_check_all_checkboxes();
@@ -62,6 +62,18 @@ $(document).ready(function () {
opendistrib_sponsorship();
});


function label_unit_reference(unit) {
if(unit == 'piece') {
return 'p.';
}
else if(unit == 'g' || unit == 'kg') {
return 'kg';
}
else {
return 'litre(s)';
}
}
function opendistrib_sponsorship() {
$('#sponsorship-link-copy').click(function() {
navigator.clipboard.writeText($(this).attr('href'));
@@ -173,6 +185,12 @@ function opendistrib_user_form_event($fieldUserEmail, $fieldSendMailWelcome) {
}
}

function opendistrib_user_credit() {
if($('body.user-credit').length) {
$('#creditform-amount').focus();
}
}

function opendistrib_producers_admin() {
$('.producer-admin-index .btn-alwaysdata, .producer-admin-index .btn-dolibarr').click(function () {
var $button = $(this);
@@ -479,10 +497,6 @@ function opendistrib_points_vente_acces_event() {
}
}

function opendistrib_commandeauto() {
$('#subscriptionform-date_begin, #subscriptionform-date_end').datepicker();
}

function opendistrib_sortable_list(element_selector, button_selector, route_ajax) {
var fixHelper = function (e, ui) {
ui.children().each(function () {
@@ -567,6 +581,7 @@ function opendistrib_features_index() {
}

function opendistrib_datepicker() {
$('#subscriptionform-date_begin, #subscriptionform-date_end').datepicker();
$('input.datepicker').datepicker({dateFormat: 'dd/mm/yy'});
}


+ 51
- 25
backend/web/js/vuejs/distribution-index.js View File

@@ -294,6 +294,21 @@ if($(selector).length) {
}
});
},
labelUnitReference: function(unit) {
return label_unit_reference(unit);
},
getProductQuantityRemainingGlobal: function(product) {
var app = this;

var productQuantityRemaining = product.quantity_remaining;

// format
if (productQuantityRemaining && product.unit != 'piece') {
productQuantityRemaining = productQuantityRemaining.toFixed(2);
}

return productQuantityRemaining;
},
initCountActiveProducts: function () {
this.countActiveProducts = 0;
for (var i = 0; i < this.products.length; i++) {
@@ -334,6 +349,7 @@ if($(selector).length) {
this.init();
},
productQuantityMaxChange: function (event) {
var app = this;
var quantityMax = event.currentTarget.value;
axios.get("ajax-process-product-quantity-max", {
params: {
@@ -343,7 +359,7 @@ if($(selector).length) {
}
})
.then(function (response) {
app.init(app.idActivePointSale);
});
},
productActiveClick: function (event) {
@@ -377,11 +393,8 @@ if($(selector).length) {
return false;
},
isProductMaximumQuantityExceeded: function (product) {
return
this.getProductDistribution(product)
&& this.getProductDistribution(product).quantity_max
&& product.quantity_ordered
&& product.quantity_ordered > this.getProductDistribution(product).quantity_max;
return product.quantity_remaining
&& product.quantity_remaining < 0;
},
pointSaleActiveClick: function (event) {
var idPointSale = event.currentTarget.getAttribute('data-id-point-sale');
@@ -493,19 +506,19 @@ if($(selector).length) {
},
updateOrderFromUrl: function () {
this.initModalFormOrder();
this.updateProductOrderPrices(false);
this.updateProductOrders(false);
},
updateOrderClick: function (event) {
var idOrder = event.currentTarget.getAttribute('data-id-order');
this.idOrderUpdate = idOrder;
this.showModalFormOrderUpdate = true;
this.initModalFormOrder();
this.updateProductOrderPrices(false);
this.updateProductOrders(false);
},
openModalFormOrderCreate: function () {
this.showModalFormOrderCreate = true;
this.initModalFormOrder();
this.updateProductOrderPrices(false);
this.updateProductOrders(false);
},
initModalFormOrder: function () {
var app = this;
@@ -829,8 +842,19 @@ if($(selector).length) {
app.init(app.idActivePointSale);
});
},

updateProductOrderPrices: function (updatePricesOnUpdateOrder) {
getProductOrderArrayRequest: function (order) {
var productOrderArrayRequest = {};
for (var key in order.productOrder) {
productOrderArrayRequest[key] = {
quantity: order.productOrder[key].quantity,
unit: order.productOrder[key].unit,
price: order.productOrder[key].price,
invoice_price: order.productOrder[key].invoice_price
};
}
return JSON.stringify(productOrderArrayRequest);
},
updateProductOrders: function (updatePricesOnUpdateOrder) {
var app = this;
app.loadingUpdateProductOrder = true;
var order = null;
@@ -853,7 +877,8 @@ if($(selector).length) {
idDistribution: app.distribution.id,
idUser: order.id_user,
idPointSale: order.id_point_sale,
idOrder: order.id
idOrder: order.id,
productOrderFormArray: app.getProductOrderArrayRequest(order)
}
})
.then(function (response) {
@@ -861,6 +886,7 @@ if($(selector).length) {
for (idProduct in response.data) {

if (app.showModalFormOrderCreate) {
Vue.set(app.orderCreate.productOrder[idProduct], 'quantity_remaining', response.data[idProduct].quantity_remaining);
Vue.set(app.orderCreate.productOrder[idProduct], 'prices', response.data[idProduct].prices);
Vue.set(app.orderCreate.productOrder[idProduct], 'active', response.data[idProduct].active);
Vue.set(app.orderCreate.productOrder[idProduct], 'price', app.getBestProductPrice(app.orderCreate, idProduct, app.orderCreate.productOrder[idProduct].quantity, false));
@@ -870,6 +896,7 @@ if($(selector).length) {
if (app.showModalFormOrderUpdate && app.idOrderUpdate) {
for (keyOrderUpdate in app.ordersUpdate) {
if (order.id == app.idOrderUpdate) {
Vue.set(app.ordersUpdate[keyOrderUpdate].productOrder[idProduct], 'quantity_remaining', response.data[idProduct].quantity_remaining);
Vue.set(app.ordersUpdate[keyOrderUpdate].productOrder[idProduct], 'prices', response.data[idProduct].prices);
Vue.set(app.ordersUpdate[keyOrderUpdate].productOrder[idProduct], 'active', response.data[idProduct].active);
Vue.set(app.ordersUpdate[keyOrderUpdate].productOrder[idProduct], 'invoice_price', response.data[idProduct].invoice_price);
@@ -916,7 +943,7 @@ if($(selector).length) {
}
})
.then(function (response) {
app.updateProductOrderPrices(false);
app.updateProductOrders(false);
appAlerts.alert('info', 'Prix facturés réinitialisés.');
});
}
@@ -1033,6 +1060,9 @@ if($(selector).length) {
}
},
methods: {
labelUnitReference: function(unit) {
return label_unit_reference(unit);
},
checkForm: function () {
this.errors = [];

@@ -1157,6 +1187,8 @@ if($(selector).length) {
Vue.set(this.order.productOrder[id_product], 'price', app.getBestProductPrice(this.order, id_product, theQuantity, false));
Vue.set(this.order.productOrder[id_product], 'price_with_tax', app.getBestProductPrice(this.order, id_product, theQuantity, true));
}

this.updateProductOrders(false);
},
getProduct: function (idProduct) {
for (var i = 0; i < this.products.length; i++) {
@@ -1215,14 +1247,14 @@ if($(selector).length) {
if (app.idActivePointSale == 0) {
app.order.id_point_sale = response.data.id_favorite_point_sale;
}
app.updateProductOrderPrices(true);
app.updateProductOrders(true);
});
},
pointSaleChange: function (event) {
this.updateProductOrderPrices(true);
this.updateProductOrders(true);
},
updateProductOrderPrices: function (updateProductOrderPrices) {
this.$emit('updateproductorderprices', updateProductOrderPrices);
updateProductOrders: function (updateProductOrders) {
this.$emit('updateproductorders', updateProductOrders);
},
updateInvoicePrices: function () {
this.$emit('updateinvoiceprices');
@@ -1247,14 +1279,8 @@ if($(selector).length) {
}
return productQuantityOrder;
},
getProductQuantityRemaining: function (product) {
var productQuantityRemaining = null;
var productQuantityMax = this.getProductQuantityMax(product);
var productQuantityOrder = this.getProductQuantityOrder(product);

if(productQuantityMax) {
productQuantityRemaining = productQuantityMax - productQuantityOrder;
}
getProductQuantityRemaining: function (order, product) {
var productQuantityRemaining = order.productOrder[product.id].quantity_remaining;

// format
if (productQuantityRemaining && product.unit != 'piece') {

+ 8
- 0
backend/web/sass/automatic-email/_form.scss View File

@@ -0,0 +1,8 @@

.automatic-email-form {
form {
label {
display: block;
}
}
}

+ 25
- 16
backend/web/sass/distribution/_index.scss View File

@@ -93,6 +93,22 @@ termes.
}
}

table td.quantity-remaining {
text-align: right;

.has-quantity, .infinite {
color: #00A65A;
}

.negative {
color: #DD4B39;
}

.infinite, .empty {
font-size: 18px;
}
}

#products {
td.quantities {
width: 100px;
@@ -184,6 +200,15 @@ termes.
text-align: center;
min-width: 50px;
}

.limit-quantity-accessories {
margin-top: 7px;
font-size: 12px;

.quantity {
font-weight: bold;
}
}
}
}
}
@@ -482,22 +507,6 @@ termes.
border-right: 0px none;
}
}

td.quantity-remaining {
text-align: right;

&.quantity-remaining, &.infinite {
color: #00A65A;
}

&.negative {
color: #DD4B39;
}

&.infinite, &.empty {
font-size: 18px;
}
}
}

.actions-form {

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

@@ -1555,4 +1555,5 @@ a.btn, button.btn {
@import "feature-admin/_index.scss";
@import "setting/_form.scss";
@import "dashboard-admin/_index.scss" ;
@import "automatic-email/_form.scss" ;
@import "_responsive.scss" ;

+ 4
- 0
backend/web/sass/user/_form.scss View File

@@ -12,6 +12,10 @@
label.control-label {
display: none;
}

.field-user-exclusive_access_selected_points_sale label.control-label {
display: block;
}
}

.panel-user-groups {

+ 35
- 0
common/components/ActiveDataProvider.php View File

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

namespace common\components;

class ActiveDataProvider extends \yii\data\ActiveDataProvider
{
public function filterByCallback($callback)
{
$filtered_models = $filtered_keys = [];
// la pagination ne fonctionne pas avec ce système de filtre : solution alternative sans pagination
// @TODO : faire en sorte que la pagination fonctionne avec les filtres par callback
$this->pagination = false;
$have_been_filtered = false;
$this->prepare(true); // force read of next page
$non_filtered_models = $this->getModels();

foreach($non_filtered_models as $model) {
$filterModel = $callback($model);

if($filterModel) {
$have_been_filtered = true;
$this->setTotalCount($this->getTotalCount() - 1);
}
else {
$filtered_models[] = $model;
$filtered_keys[] = $model->id;
}
}

if($have_been_filtered) {
$this->setModels($filtered_models);
$this->setKeys($filtered_keys);
}
}
}

+ 7
- 6
common/components/BulkMailer/BulkMailerBrevo.php View File

@@ -2,27 +2,28 @@

namespace common\components\BulkMailer;

use domain\Communication\Email\Email;
use domain\User\User\UserSolver;
use GuzzleHttp\Client;
use Psr\Http\Message\ResponseInterface;

class BulkMailerBrevo implements BulkMailerInterface
{
public function sendEmails(array $contactsArray, string $fromName, string $fromEmail, string $subject, string $htmlContent, string $textContent = null)
public function sendEmail(Email $email, array $usersArray)
{
$userSolver = UserSolver::getInstance();
$client = new Client();
$data = [
'sender' => [
'name' => $fromName,
'email' => $fromEmail
'name' => $email->getFromName(),
'email' => $email->getFromEmail()
],
'bcc' => [],
'subject' => $subject,
'htmlContent' => $htmlContent
'subject' => $email->getSubject(),
'htmlContent' => $email->getHtmlContent()
] ;

foreach($contactsArray as $user) {
foreach($usersArray as $user) {
$data['bcc'][] = [
'name' => $userSolver->getUsernameFromArray($user),
'email' => $user['email']

+ 3
- 1
common/components/BulkMailer/BulkMailerInterface.php View File

@@ -2,10 +2,12 @@

namespace common\components\BulkMailer;

use domain\Communication\Email\Email;

interface BulkMailerInterface
{
const MAILJET = 'mailjet';
const BREVO = 'brevo';

public function sendEmails(array $contactsArray, string $fromName, string $fromEmail, string $subject, string $htmlContent, string $textContent = null);
public function sendEmail(Email $email, array $usersArray);
}

+ 8
- 7
common/components/BulkMailer/BulkMailerMailjet.php View File

@@ -3,11 +3,12 @@
namespace common\components\BulkMailer;

use common\helpers\Mailjet;
use domain\Communication\Email\Email;
use Mailjet\Client;

class BulkMailerMailjet implements BulkMailerInterface
{
public function sendEmails(array $contactsArray, string $fromName, string $fromEmail, string $subject, string $htmlContent, string $textContent = null)
public function sendEmail(Email $email, array $usersArray)
{
$client = new Client(
Mailjet::getApiKey('public'),
@@ -18,11 +19,11 @@ class BulkMailerMailjet implements BulkMailerInterface

$data = ['Messages' => []] ;

foreach($contactsArray as $user) {
foreach($usersArray as $user) {
$data['Messages'][] = [
'From' => [
'Email' => $fromEmail,
'Name' => $fromName
'Email' => $email->getFromEmail(),
'Name' => $email->getFromName()
],
'To' => [
[
@@ -30,9 +31,9 @@ class BulkMailerMailjet implements BulkMailerInterface
'Name' => $user['name'].' '.$user['lastname']
]
],
'Subject' => $subject,
'HTMLPart' => $htmlContent,
'TextPart' => $textContent
'Subject' => $email->getSubject(),
'HTMLPart' => $email->getHtmlContent(),
'TextPart' => $email->getTextContent()
] ;

if(count($data['Messages']) == 50) {

+ 3
- 2
common/components/BulkMailer/BulkMailerProxy.php View File

@@ -2,6 +2,7 @@

namespace common\components\BulkMailer;

use domain\Communication\Email\Email;
use domain\Setting\AdminSettingBag;
use yii\base\ErrorException;

@@ -22,8 +23,8 @@ class BulkMailerProxy implements BulkMailerInterface
}
}

public function sendEmails(array $contactsArray, string $fromName, string $fromEmail, string $subject, string $htmlContent, string $textContent = null)
public function sendEmail(Email $email, array $usersArray)
{
$this->getBulkMailer()->sendEmails($contactsArray, $fromName, $fromEmail, $subject, $htmlContent, $textContent);
$this->getBulkMailer()->sendEmail($email, $usersArray);
}
}

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

@@ -26,6 +26,8 @@ class BusinessLogic
$this->getUserProducerModule(),
$this->getUserPointSaleModule(),
$this->getUserModule(),
$this->getEmailModule(),
$this->getAutomaticEmailModule(),
$this->getUserMessageModule(),
$this->getPointSaleDistributionModule(),
$this->getProductAccessoryModule(),

+ 12
- 0
common/components/BusinessLogicTrait.php View File

@@ -2,6 +2,7 @@

namespace common\components;

use domain\Communication\Email\EmailModule;
use domain\Config\TaxRate\TaxRateModule;
use domain\Config\Unit\UnitModule;
use domain\Distribution\Distribution\DistributionModule;
@@ -13,6 +14,7 @@ use domain\Document\Invoice\InvoiceModule;
use domain\Document\Quotation\QuotationModule;
use domain\Feature\Feature\FeatureModule;
use domain\Feature\FeatureProducer\FeatureProducerModule;
use domain\Communication\AutomaticEmail\AutomaticEmailModule;
use domain\Opinion\OpinionModule;
use domain\Order\Order\OrderModule;
use domain\Order\OrderStatus\OrderStatusModule;
@@ -44,6 +46,16 @@ use domain\User\UserUserGroup\UserUserGroupModule;

trait BusinessLogicTrait
{
public function getEmailModule(): EmailModule
{
return EmailModule::getInstance();
}

public function getAutomaticEmailModule(): AutomaticEmailModule
{
return AutomaticEmailModule::getInstance();
}

public function getProductAccessoryModule(): ProductAccessoryModule
{
return ProductAccessoryModule::getInstance();

+ 25
- 0
common/components/Date.php View File

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

namespace common\components;

class Date
{
public static function getDaysOfWeekArray(): array
{
return [
1 => 'Lundi',
2 => 'Mardi',
3 => 'Mercredi',
4 => 'Jeudi',
5 => 'Vendredi',
6 => 'Samedi',
7 => 'Dimanche'
];
}

public static function getDayOfWeekStringByNumber(int $dayOfWeekNumber): string
{
$daysOfWeekArray = self::getDaysOfWeekArray();
return $daysOfWeekArray[$dayOfWeekNumber];
}
}

+ 1
- 1
common/components/UrlManagerCommon.php View File

@@ -67,7 +67,7 @@ class UrlManagerCommon extends UrlManager
{
if ($this->_hostInfo === null)
{
$secure = Yii::$app->getRequest()->getIsSecureConnection();
$secure = Yii::$app->getRequest()->isConsoleRequest ? false : Yii::$app->getRequest()->getIsSecureConnection();
$http = $secure ? 'https' : 'http';

if (isset($_SERVER['HTTP_HOST'])) {

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

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

return [
'version' => '24.6.D',
'version' => '24.7.A',
'maintenanceMode' => false,
'siteName' => 'Souke',
'tinyMcePlugins' => 'preview searchreplace autolink autosave save directionality code visualblocks visualchars fullscreen image link lists wordcount help',

+ 1
- 1
common/versions/24.6.D.php View File

@@ -10,7 +10,7 @@ version(
"[Administration] Utilisateur > Créer (en arrivant de la création d'une commande) : retour automatique à la création de la commande",
],
[
"[Administration] Tableau de bord : corectif affichage des commandes modifiées issues des abonnements"
"[Administration] Tableau de bord : correctif affichage des commandes modifiées issues des abonnements"
]
],
[

+ 28
- 0
common/versions/24.7.A.php View File

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

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

version(
'01/07/2024',
[
[
"Gestion des accessoires produits (module payant) : possibilité de limiter les quantités de produits commandables en fonction des quantités d'accessoires disponibles (exemple : moules)",
"Système de parrainage entre producteurs (voir dans 'Parrainage')",
"[Administration] Statistiques > Export paiements : export CSV des paiements sur une période donnée",
"Utilisateurs : accès exclusif à certains points de vente (Profil utilisateur > Points de vente : champs 'Accès exclusif aux points de vente sélectionnés')",
],
[
"[Administration] Utilisateurs > Cagnotte : curseur placé automatiquement dans le champs 'Montant'"
]
],
[
[
],
[
"[Administration] Déliverabilité emails : ajout colonne 'Note emails'"
]
],
$userCurrent
);

?>

+ 36
- 0
console/commands/AutomaticEmailController.php View File

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

namespace console\commands;

use domain\Communication\AutomaticEmail\AutomaticEmailModule;
use domain\Communication\Email\EmailModule;
use domain\Producer\Producer\ProducerModule;
use yii\console\Controller;

class AutomaticEmailController extends Controller
{
// ./yii automatic-email/send
public function actionSend()
{
$automaticEmailModule = AutomaticEmailModule::getInstance();
$producerModule = ProducerModule::getInstance();
$emailModule = EmailModule::getInstance();

$producersArray = $producerModule->getRepository()->findProducers();
foreach($producersArray as $producer) {
\Yii::$app->logic->setProducerContext($producer);
$automaticEmailsArray = $automaticEmailModule->getRepository()->findAutomaticEmails();
foreach($automaticEmailsArray as $automaticEmail) {
$distribution = $automaticEmailModule->getResolver()->getMatchedDistribution($automaticEmail);
if($distribution) {
$email = $automaticEmailModule->getManager()->createEmailFromAutomaticEmail($automaticEmail, $distribution);
$usersArray = $emailModule->getContactListResolver()->search($producer, $distribution);
$emailModule->getBulkMailer()->sendEmail($email, $usersArray);
echo 'Email automatique "'.$automaticEmail->getSubject().'" envoyé à '.count($usersArray)." utilisateur(s)\n";
}
}
}
}
}

?>

+ 2
- 0
console/config/main.php View File

@@ -36,6 +36,8 @@ pris connaissance de la licence CeCILL, et que vous en avez accepté les
termes.
*/

setlocale(LC_TIME, 'fr_FR.utf8','fra');

$params = array_merge(
require(__DIR__ . '/../../common/config/params.php'),
require(__DIR__ . '/../../common/config/params-local.php'),

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

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

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

/**
* Class m240626_133528_add_column_user_exclusive_access_selected_points_sale
*/
class m240626_133528_add_column_user_exclusive_access_selected_points_sale extends Migration
{
/**
* {@inheritdoc}
*/
public function safeUp()
{
$this->addColumn('user_producer', 'exclusive_access_selected_points_sale', Schema::TYPE_BOOLEAN);
}

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

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

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

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

/**
* Class m240627_064315_create_table_automatic_email
*/
class m240627_064315_create_table_automatic_email extends Migration
{
/**
* {@inheritdoc}
*/
public function safeUp()
{
$this->createTable('automatic_email', [
'id' => 'pk',
'id_producer' => Schema::TYPE_INTEGER.' NOT NULL',
'day' => Schema::TYPE_INTEGER.' NOT NULL',
'delay_before_distribution' => Schema::TYPE_INTEGER.' NOT NULL',
'subject' => Schema::TYPE_STRING.' NOT NULL',
'message' => Schema::TYPE_TEXT.' NOT NULL',
'integrate_product_list' => Schema::TYPE_BOOLEAN,
'status' => Schema::TYPE_INTEGER.' NOT NULL',
]);

$this->addForeignKey('fk_automatic_email_id_producer', 'automatic_email', 'id_producer', 'producer', 'id');
}

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

+ 152
- 0
domain/Communication/AutomaticEmail/AutomaticEmail.php View File

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

namespace domain\Communication\AutomaticEmail;

use common\components\ActiveRecordCommon;
use common\components\Date;
use domain\Producer\Producer\Producer;
use yii\db\ActiveQuery;

class AutomaticEmail extends ActiveRecordCommon
{
public static function tableName()
{
return 'automatic_email';
}

public function rules()
{
return [
[['id_producer', 'day', 'subject', 'message', 'status'], 'required'],
[['subject', 'message'], 'string'],
[['id_producer', 'day', 'delay_before_distribution', 'status'], 'integer'],
[['integrate_product_list'], 'boolean']
];
}

public function attributeLabels()
{
return [
'id_producer' => 'Producteur',
'day' => 'Jour de distribution',
'delay_before_distribution' => 'Envoi du message',
'subject' => 'Sujet',
'message' => 'Message',
'integrate_product_list' => 'Intégrer la liste des produits au message',
'status' => 'Statut'
];
}

/* Getters / Setters */

public function getId(): ?int
{
return $this->id;
}

public function getProducer(): Producer
{
return $this->producerRelation;
}

public function setProducer(Producer $producer): self
{
$this->populateFieldObject('id_producer', 'producerRelation', $producer);
return $this;
}

public function getDay(): int
{
return $this->day;
}

public function setDay(int $day): self
{
$this->day = $day;
return $this;
}

public function getDelayBeforeDistribution(): int
{
return $this->delay_before_distribution;
}

public function setDelayBeforeDistribution(int $delayBeforeDistribution): self
{
$this->delay_before_distribution = $delayBeforeDistribution;
return $this;
}

public function getSubject(): string
{
return $this->subject;
}

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

public function getMessage(): string
{
return $this->message;
}

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

public function getIntegrateProductList(): ?bool
{
return $this->integrate_product_list;
}

public function setIntegrateProductList(?bool $integrateProductList): self
{
$this->integrate_product_list = $integrateProductList;
return $this;
}

public function getStatus(): int
{
return $this->status;
}

public function setStatus(int $status): self
{
$this->status = $status;
return $this;
}

/* Relations */

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

/* Méthodes */

public function getDayAsString(): string
{
return Date::getDayOfWeekStringByNumber($this->getDay());
}

public function getDelayBeforeDistributionAsString(): string
{
return $this->getDelayBeforeDistribution().' jour(s) avant';
}

public function getStatusAsHtml(): string
{
if($this->getStatus()) {
return '<span class="label label-success">Activé</span>';
}
else {
return '<span class="label label-danger">Désactivé</span>';
}
}
}

+ 19
- 0
domain/Communication/AutomaticEmail/AutomaticEmailBuilder.php View File

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

namespace domain\Communication\AutomaticEmail;

use domain\_\AbstractBuilder;
use domain\_\StatusInterface;
use domain\Producer\Producer\Producer;

class AutomaticEmailBuilder extends AbstractBuilder
{
public function instanciateAutomaticEmail(Producer $producer): AutomaticEmail
{
$automaticEmail = new AutomaticEmail();
$automaticEmail->setProducer($producer);
$automaticEmail->setDelayBeforeDistribution(2);
$automaticEmail->setStatus(StatusInterface::STATUS_ONLINE);
return $automaticEmail;
}
}

+ 13
- 0
domain/Communication/AutomaticEmail/AutomaticEmailDefinition.php View File

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

namespace domain\Communication\AutomaticEmail;

use domain\_\AbstractDefinition;

class AutomaticEmailDefinition extends AbstractDefinition
{
public function getEntityFqcn(): string
{
return AutomaticEmail::class;
}
}

+ 54
- 0
domain/Communication/AutomaticEmail/AutomaticEmailManager.php View File

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

namespace domain\Communication\AutomaticEmail;

use domain\_\AbstractManager;
use domain\Communication\Email\Email;
use domain\Communication\Email\EmailGenerator;
use domain\Distribution\Distribution\Distribution;
use domain\Producer\Producer\Producer;

class AutomaticEmailManager extends AbstractManager
{
protected AutomaticEmailBuilder $automaticEmailBuilder;
protected EmailGenerator $emailGenerator;

public function loadDependencies(): void
{
$this->automaticEmailBuilder = $this->loadService(AutomaticEmailBuilder::class);
$this->emailGenerator = $this->loadService(EmailGenerator::class);
}

public function createAutomaticEmail(
Producer $producer,
int $day,
int $delayBeforeDistribution,
string $subject,
string $message,
bool $integrateProductList = null
): AutomaticEmail
{
$automaticEmail = $this->automaticEmailBuilder->instanciateAutomaticEmail($producer);
$automaticEmail->setDay($day);
$automaticEmail->setDelayBeforeDistribution($delayBeforeDistribution);
$automaticEmail->setSubject($subject);
$automaticEmail->setMessage($message);
$automaticEmail->setIntegrateProductList($integrateProductList);

$automaticEmail->save();

return $automaticEmail;
}

public function createEmailFromAutomaticEmail(AutomaticEmail $automaticEmail, Distribution $distribution): Email
{
return $this->emailGenerator->createEmail(
$automaticEmail->getSubject(),
$automaticEmail->getMessage(),
$automaticEmail->getIntegrateProductList(),
$automaticEmail->getProducer(),
$distribution
);
}

}

+ 44
- 0
domain/Communication/AutomaticEmail/AutomaticEmailModule.php View File

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

namespace domain\Communication\AutomaticEmail;

use domain\_\AbstractModule;

class AutomaticEmailModule extends AbstractModule
{
public function getServices(): array
{
return [
AutomaticEmailDefinition::class,
AutomaticEmailBuilder::class,
AutomaticEmailRepository::class,
AutomaticEmailResolver::class,
AutomaticEmailManager::class
];
}

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

public function getBuilder(): AutomaticEmailBuilder
{
return AutomaticEmailBuilder::getInstance();
}

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

public function getResolver(): AutomaticEmailResolver
{
return AutomaticEmailResolver::getInstance();
}

public function getManager(): AutomaticEmailManager
{
return AutomaticEmailManager::getInstance();
}
}

+ 43
- 0
domain/Communication/AutomaticEmail/AutomaticEmailRepository.php View File

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

namespace domain\Communication\AutomaticEmail;

use domain\_\AbstractRepository;
use domain\_\RepositoryQueryInterface;

class AutomaticEmailRepository extends AbstractRepository
{
protected AutomaticEmailRepositoryQuery $query;

public function loadDependencies(): void
{
$this->loadQuery(AutomaticEmailRepositoryQuery::class);
}

public function getDefaultOptionsSearch(): array
{
return [
self::WITH => [],
self::JOIN_WITH => [],
self::ORDER_BY => 'day ASC',
self::ATTRIBUTE_ID_PRODUCER => 'automatic_email.id_producer'
];
}

public function findOneAutomaticEmailById(int $id): ?AutomaticEmail
{
return $this->createDefaultQuery()
->filterById($id)
->findOne();
}

public function queryAutomaticEmails(): RepositoryQueryInterface
{
return $this->createDefaultQuery();
}

public function findAutomaticEmails(): array
{
return $this->queryAutomaticEmails()->find();
}
}

+ 15
- 0
domain/Communication/AutomaticEmail/AutomaticEmailRepositoryQuery.php View File

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

namespace domain\Communication\AutomaticEmail;

use domain\_\AbstractRepositoryQuery;

class AutomaticEmailRepositoryQuery extends AbstractRepositoryQuery
{
protected AutomaticEmailDefinition $definition;

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

+ 36
- 0
domain/Communication/AutomaticEmail/AutomaticEmailResolver.php View File

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

namespace domain\Communication\AutomaticEmail;

use domain\_\AbstractResolver;
use domain\Distribution\Distribution\Distribution;
use domain\Distribution\Distribution\DistributionRepository;
use domain\Producer\Producer\ProducerSolver;

class AutomaticEmailResolver extends AbstractResolver
{
protected DistributionRepository $distributionRepository;
protected ProducerSolver $producerSolver;

public function loadDependencies(): void
{
$this->distributionRepository = $this->loadService(DistributionRepository::class);
$this->producerSolver = $this->loadService(ProducerSolver::class);
}

public function getMatchedDistribution(AutomaticEmail $automaticEmail): ?Distribution
{
$date = (new \DateTime('+'.$automaticEmail->getDelayBeforeDistribution().' days'));
$dateFormat = $date->format('Y-m-d');
$distribution = $this->distributionRepository->findOneDistribution($dateFormat);

if($distribution
&& $distribution->active
&& $date->format('N') == $automaticEmail->getDay()
&& !$this->producerSolver->isOnLeavePeriodByDistribution($distribution)) {
return $distribution;
}

return null;
}
}

+ 81
- 0
domain/Communication/Email/ContactListResolver.php View File

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

namespace domain\Communication\Email;

use common\helpers\GlobalParam;
use domain\_\AbstractResolver;
use domain\Distribution\Distribution\Distribution;
use domain\PointSale\PointSale\PointSale;
use domain\Producer\Producer\Producer;
use domain\User\User\UserRepository;

class ContactListResolver extends AbstractResolver
{
protected UserRepository $userRepository;

public function loadDependencies(): void
{
$this->userRepository = $this->loadService(UserRepository::class);
}

public function search(
Producer $producer,
Distribution $distribution = null,
PointSale $pointSale = null,
bool $usersPointSaleLink = false,
bool $usersPointSaleHasOrder = false,
bool $isSubscriber = false,
bool $isInactive = false
): array
{
if ($pointSale && !$usersPointSaleLink && !$usersPointSaleHasOrder) {
$usersPointSaleLink = 1;
}
if ($distribution && !$usersPointSaleLink && !$usersPointSaleHasOrder) {
$usersPointSaleLink = 1;
}

if($distribution) {
$users = [];

foreach($distribution->pointSaleDistribution as $pointSaleDistribution) {
if($pointSaleDistribution->delivery) {
$usersPointSaleArray = $this->userRepository->queryUsersBy([
'id_producer' => $producer->id,
'id_point_sale' => $pointSaleDistribution->id_point_sale,
'users_point_sale_link' => $usersPointSaleLink,
'users_point_sale_has_order' => $usersPointSaleHasOrder,
'newsletter' => true
])->all();

foreach($usersPointSaleArray as $user) {
$users[$user['id']] = $user;
}
}
}
}
else {
$users = $this->userRepository->queryUsersBy([
'id_producer' => $producer->id,
'id_point_sale' => $pointSale ? $pointSale->id : null,
'users_point_sale_link' => $usersPointSaleLink,
'users_point_sale_has_order' => $usersPointSaleHasOrder,
'subscribers' => $isSubscriber,
'inactive' => $isInactive,
'newsletter' => true
])->all();
}

$usersArray = [];
foreach ($users as $user) {
if (isset($user['email']) && strlen($user['email']) > 0) {
if($producer->isDemoAccount()) {
$user['email'] = \Yii::$app->faker->email();
}
$usersArray[] = $user;
}
}

return $usersArray;
}
}

+ 67
- 0
domain/Communication/Email/Email.php View File

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

namespace domain\Communication\Email;

class Email
{
protected string $fromName;
protected string $fromEmail;
protected string $subject;
protected string $htmlContent;
protected string $textContent;

public function getFromName(): string
{
return $this->fromName;
}

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

public function getFromEmail(): string
{
return $this->fromEmail;
}

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

public function getSubject(): string
{
return $this->subject;
}

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

public function getHtmlContent(): string
{
return $this->htmlContent;
}

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

public function getTextContent(): string
{
return $this->textContent;
}

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

+ 27
- 0
domain/Communication/Email/EmailBuilder.php View File

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

namespace domain\Communication\Email;

use domain\_\AbstractBuilder;

class EmailBuilder extends AbstractBuilder
{
public function instanciateEmail(
string $fromName,
string $fromEmail,
string $subject,
string $htmlContent,
string $textContent
): Email
{
$email = new Email();

$email->setFromName($fromName);
$email->setFromEmail($fromEmail);
$email->setSubject($subject);
$email->setHtmlContent($htmlContent);
$email->setTextContent($textContent);

return $email;
}
}

+ 145
- 0
domain/Communication/Email/EmailGenerator.php View File

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

namespace domain\Communication\Email;

use common\helpers\Price;
use domain\_\AbstractResolver;
use domain\Config\Unit\UnitDefinition;
use domain\Distribution\Distribution\Distribution;
use domain\Distribution\Distribution\DistributionSolver;
use domain\Producer\Producer\Producer;
use domain\Producer\Producer\ProducerSolver;
use domain\Product\Product\Product;
use domain\Product\Product\ProductSolver;
use yii\helpers\Html;

class EmailGenerator extends AbstractResolver
{
protected EmailBuilder $emailBuilder;
protected ProductSolver $productSolver;
protected DistributionSolver $distributionSolver;
protected ProducerSolver $producerSolver;

public function loadDependencies(): void
{
$this->emailBuilder = $this->loadService(EmailBuilder::class);
$this->productSolver = $this->loadService(ProductSolver::class);
$this->distributionSolver = $this->loadService(DistributionSolver::class);
$this->producerSolver = $this->loadService(ProducerSolver::class);
}

public function createEmail(
string $subject,
string $message,
bool $integrateProductList,
Producer $producer = null,
Distribution $distribution = null
): Email
{
$messageAutoText = '' ;
$messageAutoHtml = '' ;

$messageAutoHtml .= ' <style type="text/css">
h1, h2, h3, h4, h5, h6 {
padding: 0px;
margin: 0px;
margin-bottom: 10px;
}
p {
margin: 0px;
padding: 0px;
margin-bottom: 5px;
}
</style>';

if($distribution) {
$messageAutoText = '

' ;
$messageAutoHtml .= '<br /><br />' ;

$linkOrder = $this->distributionSolver->getLinkOrder($distribution);
$dateOrder = strftime('%A %d %B %Y', strtotime($distribution->date)) ;
$messageAutoHtml .= '<a href="'.$linkOrder.'">Passer ma commande du '.$dateOrder.'</a>' ;
$messageAutoText .= 'Suivez ce lien pour passer votre commande du '.$dateOrder.' :
'.$linkOrder ;

if($integrateProductList) {
$productsArray = Product::find()
->where([
'id_producer' => $producer->id,
])
->andWhere('status >= :status')
->addParams(['status' => Product::STATUS_OFFLINE])
->innerJoinWith(['productDistribution' => function($query) use($distribution) {
$query->andOnCondition([
'product_distribution.id_distribution' => $distribution->id,
'product_distribution.active' => 1
]);
}])
->orderBy('product.name ASC')
->all();

if(count($productsArray) > 0) {

$messageAutoHtml .= '<br /><br />Produits disponibles : <br /><ul>' ;
$messageAutoText .= '

Produits disponibles :
' ;
foreach($productsArray as $product) {

$productDescription = $product->name ;
if(strlen($product->description)) {
$productDescription .= ' / '.$product->description ;
}
if($product->price) {
$productDescription .= ' / '.Price::format($this->productSolver->getPriceWithTax($product)) ;
$productDescription .= ' ('. $this->productSolver->strUnit($product, UnitDefinition::WORDING_UNIT).')' ;
}

$messageAutoText .= '- '.$productDescription.'
' ;
$messageAutoHtml .= '<li>'.Html::encode($productDescription).'</li>' ;
}
$messageAutoHtml .= '</ul>' ;
}
}
}

if($producer) {
$fromEmail = $this->producerSolver->getProducerEmailPlatform($producer) ;
$fromName = $producer->name ;

$linkProducer = 'https://'.$producer->slug.'.souke.fr';
$linkUnsubscribe = \Yii::$app->urlManagerProducer->createAbsoluteUrl(['newsletter/unsubscribe', 'slug_producer' => $producer->slug]);

// Message inscription newsletter
$messageAutoText .= "

--
Boutique : ".$linkProducer."
Me désinscrire : ".$linkUnsubscribe;

$messageAutoHtml .= "<br /><br />--<br>";
$messageAutoHtml .= "Boutique : <a href=\"".$linkProducer."\">".$linkProducer."</a><br>";
$messageAutoHtml .= "Me désinscrire : <a href=\"".$linkUnsubscribe."\">".$linkUnsubscribe."</a>";
}
else {
$fromEmail = 'contact@souke.fr' ;
$fromName = 'Souke' ;
}

$htmlContent = $message.$messageAutoHtml;
$textContent = strip_tags($message).$messageAutoText;

return $this->emailBuilder->instanciateEmail(
$fromName,
$fromEmail,
$subject,
$htmlContent,
$textContent
);
}
}

+ 43
- 0
domain/Communication/Email/EmailModule.php View File

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

namespace domain\Communication\Email;

use common\components\BulkMailer\BulkMailerInterface;
use domain\_\AbstractService;

class EmailModule extends AbstractService
{
public function getEntityFqcn(): string
{
return '';
}

public function getServices(): array
{
return [
EmailBuilder::class,
ContactListResolver::class,
EmailGenerator::class,
];
}

public function getBuilder(): EmailBuilder
{
return EmailBuilder::getInstance();
}

public function getGenerator(): EmailGenerator
{
return EmailGenerator::getInstance();
}

public function getContactListResolver(): ContactListResolver
{
return ContactListResolver::getInstance();
}

public function getBulkMailer(): BulkMailerInterface
{
return \Yii::$app->bulkMailer;
}
}

+ 14
- 11
domain/Document/DeliveryNote/DeliveryNoteSearch.php View File

@@ -38,8 +38,8 @@

namespace domain\Document\DeliveryNote;

use common\components\ActiveDataProvider;
use common\helpers\GlobalParam;
use yii\data\ActiveDataProvider;

class DeliveryNoteSearch extends DeliveryNote
{
@@ -109,18 +109,21 @@ class DeliveryNoteSearch extends DeliveryNote
$query->andFilterWhere(['like', 'distribution.date', date('Y-m-d', strtotime(str_replace('/', '-', $this->date_distribution)))]);
}

// filtre facture (oui / non)
$models = $dataProvider->getModels();
foreach($models as $index => $deliveryNote) {
if(!is_null($this->with_invoice) && is_numeric($this->with_invoice)) {
$invoice = $deliveryNoteModule->getSolver()->getInvoice($deliveryNote);
if(($this->with_invoice && !$invoice) || (!$this->with_invoice && $invoice)) {
unset($models[$index]);
// filtre avec ou sans facture
$withInvoice = $this->with_invoice;
if(is_numeric($withInvoice)) {
$dataProvider->filterByCallback(function($model) use ($withInvoice) {
$deliveryNoteModule = DeliveryNoteModule::getInstance();
$filterModel = false;
if(is_numeric($withInvoice)) {
$invoice = $deliveryNoteModule->getSolver()->getInvoice($model);
if(($withInvoice && !$invoice) || (!$withInvoice && $invoice)) {
$filterModel = true;
}
}
}
return $filterModel;
});
}
$dataProvider->setModels($models);


return $dataProvider;
}

+ 18
- 16
domain/Document/Invoice/InvoiceSearch.php View File

@@ -38,20 +38,20 @@

namespace domain\Document\Invoice;

use common\components\ActiveDataProvider;
use common\helpers\GlobalParam;
use yii\data\ActiveDataProvider;
use yii\data\Sort;

class InvoiceSearch extends Invoice
{
var $paid = null;
var $is_paid;
var $username;

public function rules()
{
return [
[['paid'], 'safe'],
[['is_sent'], 'boolean'],
[[], 'safe'],
[['is_sent', 'is_paid'], 'boolean'],
[['comment', 'address', 'status', 'username', 'date'], 'string'],
[['name', 'reference', 'username'], 'string', 'max' => 255],
];
@@ -84,7 +84,6 @@ class InvoiceSearch extends Invoice
->with($optionsSearch['with'])
->joinWith($optionsSearch['join_with'])
->where(['invoice.id_producer' => GlobalParam::getCurrentProducerId()])
//->orderBy('invoice.status ASC, invoice.reference DESC')
->orderBy($sort->orders)
->groupBy('invoice.id');

@@ -116,7 +115,7 @@ class InvoiceSearch extends Invoice
}

// filtre envoyé
if(!is_null($this->is_sent) && is_numeric($this->is_sent)) {
if(is_numeric($this->is_sent)) {
if($this->is_sent) {
$query->andWhere(['invoice.is_sent' => 1]);
}
@@ -125,18 +124,21 @@ class InvoiceSearch extends Invoice
}
}

// filter payé / non payé
// @TODO : comprendre pourquoi la pagination ne suit pas
$models = $dataProvider->getModels();
foreach($models as $index => $invoice) {
if(!is_null($this->paid) && is_numeric($this->paid)) {
$isInvoicePaid = $invoiceModule->getSolver()->isInvoicePaid($invoice);
if(($this->paid && !$isInvoicePaid) || (!$this->paid && $isInvoicePaid)) {
unset($models[$index]);
// filtre payé / non payé
$isPaid = $this->is_paid;
if(is_numeric($isPaid)) {
$dataProvider->filterByCallback(function($model) use ($isPaid) {
$filterModel = false;
$invoiceModule = InvoiceModule::getInstance();
if(is_numeric($isPaid)) {
$isInvoicePaid = $invoiceModule->getSolver()->isInvoicePaid($model);
if(($isPaid && !$isInvoicePaid) || (!$isPaid && $isInvoicePaid)) {
$filterModel = true;
}
}
}
return $filterModel;
});
}
$dataProvider->setModels($models);

return $dataProvider;
}

+ 2
- 0
domain/Feature/Feature/Feature.php View File

@@ -55,6 +55,8 @@ class Feature extends ActiveRecordCommon
const ALIAS_SPONSORSHIP = 'sponsorship';
const ALIAS_BRIDGE_EVOLIZ = 'bridge_evoliz';
const ALIAS_PRODUCT_ACCESSORY = 'product_accessory';
const ALIAS_FORUM = 'forum';
const ALIAS_AUTOMATIC_EMAIL = 'automatic_email';

/**
* @inheritdoc

+ 2
- 0
domain/Feature/Feature/FeatureDefinition.php View File

@@ -25,6 +25,8 @@ class FeatureDefinition extends AbstractDefinition
Feature::ALIAS_BRIDGE_SUMUP => "Pont vers SumUp",
Feature::ALIAS_BRIDGE_EVOLIZ => "Pont vers Evoliz",
Feature::ALIAS_PRODUCT_ACCESSORY => 'Accessoires produits',
Feature::ALIAS_FORUM => 'Forum',
Feature::ALIAS_AUTOMATIC_EMAIL => 'Emails automatiques'
];
}
}

+ 60
- 0
domain/Order/Order/OrderBuilder.php View File

@@ -31,6 +31,8 @@ use domain\PointSale\UserPointSale\UserPointSaleRepository;
use domain\Producer\Producer\Producer;
use domain\Producer\Producer\ProducerRepository;
use domain\Producer\Producer\ProducerSolver;
use domain\Product\Product\Product;
use domain\Product\Product\ProductRepository;
use domain\Product\Product\ProductSolver;
use domain\Subscription\Subscription\Subscription;
use domain\Subscription\Subscription\SubscriptionBuilder;
@@ -67,6 +69,7 @@ class OrderBuilder extends AbstractBuilder
protected DocumentSolver $documentSolver;
protected ProducerSolver $producerSolver;
protected OrderStatusRepository $orderStatusRepository;
protected ProductRepository $productRepository;

public function loadDependencies(): void
{
@@ -93,6 +96,7 @@ class OrderBuilder extends AbstractBuilder
$this->documentSolver = $this->loadService(DocumentSolver::class);
$this->producerSolver = $this->loadService(ProducerSolver::class);
$this->orderStatusRepository = $this->loadService(OrderStatusRepository::class);
$this->productRepository = $this->loadService(ProductRepository::class);
}

public function instanciateOrder(Distribution $distribution): Order
@@ -118,6 +122,62 @@ class OrderBuilder extends AbstractBuilder
return $order;
}

public function instanciateOrderFromProductOrdersArray(array $productOrdersArray, Order $order = null): Order
{
if(!$order) {
$order = new Order();
$this->initOrderStatus($order);
}

$productOrderObjectArray = [];
foreach($productOrdersArray as $idProduct => $productOrder) {
if($productOrder['quantity']) {
$product = $this->productRepository->findOneProductById($idProduct);
$productOrderObject = $this->productOrderBuilder->instanciateProductOrder(
$order,
$product,
$productOrder['quantity'],
0
);
$productOrderObjectArray[] = $productOrderObject;
}
}

$order->populateRelation('productOrder', $productOrderObjectArray);

return $order;
}

public function addOrUpdateOrIgnoreOrderFromArray(Order $orderOverride, array $ordersArray, bool $ignoreOrderCurrent = false): array
{
$override = false;
foreach($ordersArray as $key => $order) {
if($order->id == $orderOverride->id) {
if($ignoreOrderCurrent) {
unset($ordersArray[$key]);
}
else {
$ordersArray[$key] = $orderOverride;
$override = true;
}
}
}
if(!$override && !$ignoreOrderCurrent) {
$ordersArray[] = $orderOverride;
}

return $ordersArray;
}

public function deleteProductOrderQuantity(Order $order, Product $product, float $quantity)
{
foreach($order->productOrder as $productOrder) {
if($productOrder->id_product == $product->id) {
$productOrder->quantity -= $quantity;
}
}
}

public function initDateUpdate(Order $order)
{
$order->date_update = date('Y-m-d H:i:s');;

+ 78
- 13
domain/Order/Order/OrderResolver.php View File

@@ -9,27 +9,51 @@ use domain\Feature\Feature\Feature;
use domain\Feature\Feature\FeatureChecker;
use domain\Product\Accessory\Accessory;
use domain\Product\Product\Product;
use yii\base\ErrorException;
use domain\Product\Product\ProductSolver;

class OrderResolver extends AbstractResolver
{
protected ProductSolver $productSolver;
protected OrderSolver $orderSolver;
protected OrderRepository $orderRepository;
protected ProductDistributionRepository $productDistributionRepository;
protected FeatureChecker $featureChecker;
protected OrderBuilder $orderBuilder;

protected array $ordersArrayCachedByDistribution = [];

public function loadDependencies(): void
{
$this->productSolver = $this->loadService(ProductSolver::class);
$this->orderSolver = $this->loadService(OrderSolver::class);
$this->orderRepository = $this->loadService(OrderRepository::class);
$this->productDistributionRepository = $this->loadService(ProductDistributionRepository::class);
$this->featureChecker = $this->loadService(FeatureChecker::class);
$this->orderBuilder = $this->loadService(OrderBuilder::class);
}

public function getProductQuantityByDistribution(Product $product, Distribution $distribution): float
public function getProductQuantityByDistribution(
Product $product,
Distribution $distribution,
bool $inNumberOfPieces = false,
Order $orderCurrent = null
): float
{
$ordersArray = $this->orderRepository->findOrdersByDistribution($distribution);
return $this->orderSolver->getProductQuantity($product, $ordersArray);
if(!isset($this->ordersArrayCachedByDistribution[$distribution->id])) {
$this->ordersArrayCachedByDistribution[$distribution->id] = $this->orderRepository->findOrdersByDistribution($distribution);
}
$ordersArray = $this->ordersArrayCachedByDistribution[$distribution->id];
if($orderCurrent) {
$ordersArray = $this->orderBuilder->addOrUpdateOrIgnoreOrderFromArray($orderCurrent, $ordersArray);
}

$productQuantity = $this->orderSolver->getProductQuantity($product, $ordersArray);

if($inNumberOfPieces) {
$productQuantity = $this->productSolver->getNumberOfPieces($productQuantity, $product);
}

return $productQuantity;
}

public function getSmallestQuantityAccessory(Product $product): ?int
@@ -48,7 +72,6 @@ class OrderResolver extends AbstractResolver
public function getProductQuantityMax(Product $product, Distribution $distribution): ?float
{
$productDistribution = $this->productDistributionRepository->findOneProductDistribution($distribution, $product);
// @TODO : gérer via une exception
if(!$productDistribution) {
return null;
}
@@ -58,25 +81,52 @@ class OrderResolver extends AbstractResolver
if($this->featureChecker->isEnabled(Feature::ALIAS_PRODUCT_ACCESSORY)) {
$smallestQuantityAccessory = $this->getSmallestQuantityAccessory($product);
if (!is_null($smallestQuantityAccessory)) {
$quantityMax = is_null($quantityMax) ? $smallestQuantityAccessory : min($quantityMax, $smallestQuantityAccessory);
$smallestQuantityAccessory = $this->productSolver->getWeightOrNumberOfPieces($smallestQuantityAccessory, $product);
$quantityMax = is_null($quantityMax) ? $smallestQuantityAccessory
: min($quantityMax, $smallestQuantityAccessory);
}
}

return $quantityMax;
}

public function getQuantityOfAccessoryAvailableInDistribution(Accessory $accessory, Distribution $distribution): int
public function getProductQuantityMaxOrderable(Product $product, Distribution $distribution, Order $orderCurrent = null): ?float
{
$productQuantity = $this->orderSolver->getProductQuantity($product, $orderCurrent ? [$orderCurrent] : []);
$productQuantityRemaining = $this->getProductQuantityRemaining($product, $distribution, $orderCurrent);

if(is_null($productQuantityRemaining)) {
return null;
}

return $productQuantity + $productQuantityRemaining;
}

public function getQuantityOfAccessoryAvailableInDistribution(
Accessory $accessory,
Distribution $distribution,
Order $orderCurrent = null
): int
{
$quantityOfAccessoryUsed = 0;

foreach($accessory->getProductAccessories() as $productAccessory) {
$quantityOfAccessoryUsed += $this->getProductQuantityByDistribution($productAccessory->getProduct(), $distribution);
$quantityOfAccessoryUsed += $this->getProductQuantityByDistribution(
$productAccessory->getProduct(),
$distribution,
true,
$orderCurrent
);
}

return $accessory->getQuantity() - $quantityOfAccessoryUsed;
}

public function getSmallestQuantityAccessoryAvailable(Product $product, Distribution $distribution): ?int
public function getSmallestQuantityAccessoryAvailable(
Product $product,
Distribution $distribution,
Order $orderCurrent = null
): ?int
{
$smallestQuantity = null;

@@ -84,7 +134,8 @@ class OrderResolver extends AbstractResolver
foreach ($product->getProductAccessories() as $productAccessory) {
$quantityAccessoryAvailableInDistribution = $this->getQuantityOfAccessoryAvailableInDistribution(
$productAccessory->getAccessory(),
$distribution
$distribution,
$orderCurrent
);
$smallestQuantity = is_null($smallestQuantity) ? $quantityAccessoryAvailableInDistribution
: min($smallestQuantity, $quantityAccessoryAvailableInDistribution);
@@ -94,21 +145,35 @@ class OrderResolver extends AbstractResolver
return $smallestQuantity;
}

public function getProductQuantityRemaining(Product $product, Distribution $distribution): ?float
public function getProductQuantityRemaining(
Product $product,
Distribution $distribution,
Order $orderCurrent = null
): ?float
{
$productDistribution = $this->productDistributionRepository->findOneProductDistribution($distribution, $product);
if(!$productDistribution) {
return null;
}

$quantityOrder = $this->getProductQuantityByDistribution($product, $distribution);
$quantityOrder = $this->getProductQuantityByDistribution(
$product,
$distribution,
false,
$orderCurrent
);
$quantityRemaining = is_null($productDistribution->quantity_max) ? null
: ($productDistribution->quantity_max - $quantityOrder);

// Limitation par nombre d'accessoires disponibles
if($this->featureChecker->isEnabled(Feature::ALIAS_PRODUCT_ACCESSORY)) {
$smallestQuantityAccessoryAvailable = $this->getSmallestQuantityAccessoryAvailable($product, $distribution);
$smallestQuantityAccessoryAvailable = $this->getSmallestQuantityAccessoryAvailable(
$product,
$distribution,
$orderCurrent
);
if (!is_null($smallestQuantityAccessoryAvailable)) {
$smallestQuantityAccessoryAvailable = $this->productSolver->getWeightOrNumberOfPieces($smallestQuantityAccessoryAvailable, $product);
$quantityRemaining = is_null($quantityRemaining) ? $smallestQuantityAccessoryAvailable
: min($quantityRemaining, $smallestQuantityAccessoryAvailable);
}

+ 6
- 0
domain/Producer/Producer/ProducerSolver.php View File

@@ -3,6 +3,7 @@
namespace domain\Producer\Producer;

use common\helpers\GlobalParam;
use domain\Distribution\Distribution\Distribution;
use domain\User\User\User;
use domain\_\AbstractService;
use domain\_\SolverInterface;
@@ -276,6 +277,11 @@ class ProducerSolver extends AbstractService implements SolverInterface
return $producer->option_leave_period_start && $producer->option_leave_period_end;
}

public function isOnLeavePeriodByDistribution(Distribution $distribution): bool
{
return $this->isOnLeavePeriod($distribution->producer, \DateTime::createFromFormat('Y-m-d', $distribution->date));
}

public function isOnLeavePeriod(Producer $producer, \DateTime $date = null): bool
{
if(!$date) {

+ 3
- 3
domain/Product/Accessory/Accessory.php View File

@@ -20,7 +20,7 @@ class Accessory extends ActiveRecordCommon
public function rules()
{
return [
[['name', 'id_producer'], 'required'],
[['name', 'id_producer', 'quantity'], 'required'],
[['name'], 'string', 'max' => 255],
[['quantity', 'id_producer'], 'integer'],
[['selected_products_ids'], 'safe'],
@@ -65,12 +65,12 @@ class Accessory extends ActiveRecordCommon
return $this;
}

public function getQuantity(): ?int
public function getQuantity(): int
{
return $this->quantity;
}

public function setQuantity(?int $quantity): self
public function setQuantity(int $quantity): self
{
$this->quantity = $quantity;
return $this;

+ 19
- 0
domain/Product/Product/ProductSolver.php View File

@@ -25,6 +25,25 @@ class ProductSolver extends AbstractService implements SolverInterface
$this->pointSaleSolver = $this->loadService(PointSaleSolver::class);
}

public function getWeightOrNumberOfPieces(float $quantity, Product $product): float
{
// Poids total
if($product->unit != 'piece') {
$quantity = ($quantity * $product->weight) / 1000;
}

return $quantity;
}

public function getNumberOfPieces(float $quantity, Product $product): int
{
if($product->unit != 'piece') {
$quantity = $quantity / ($product->weight / 1000);
}

return $quantity;
}

public function getUnitCoefficient(Product $product): int
{
return $this->unitSolver->getUnitCoefficient($product->unit);

+ 4
- 2
domain/User/User/User.php View File

@@ -80,6 +80,7 @@ class User extends ActiveRecordCommon implements IdentityInterface
var $send_mail_welcome;
var $trust_alert;
var $trust_alert_comment;
var $exclusive_access_selected_points_sale;

/**
* @inheritdoc
@@ -108,7 +109,7 @@ class User extends ActiveRecordCommon implements IdentityInterface
[['no_mail', 'mail_distribution_monday', 'mail_distribution_tuesday', 'mail_distribution_wednesday',
'mail_distribution_thursday', 'mail_distribution_friday', 'mail_distribution_saturday',
'mail_distribution_sunday', 'is_main_contact', 'newsletter', 'exclude_export_shopping_cart_labels',
'send_mail_welcome', 'trust_alert', 'newsletter_souke', 'problem_receiving_emails'], 'boolean'],
'send_mail_welcome', 'trust_alert', 'newsletter_souke', 'problem_receiving_emails', 'exclusive_access_selected_points_sale'], 'boolean'],
[['lastname', 'name', 'phone', 'address', 'type', 'name_legal_person', 'evoliz_code', 'trust_alert_comment', 'note_emails'], 'string'],
['lastname', 'verifyOneName', 'skipOnError' => false, 'skipOnEmpty' => false],
[['email', 'email_sending_invoicing_documents'], 'email', 'message' => 'Cette adresse email n\'est pas valide'],
@@ -159,7 +160,8 @@ class User extends ActiveRecordCommon implements IdentityInterface
'trust_alert_comment' => 'Commentaire',
'newsletter_souke' => "S'abonner à l'infolettre de Souke",
'problem_receiving_emails' => "Rencontre des problèmes pour recevoir les emails",
'note_emails' => "Note emails"
'note_emails' => "Note emails",
'exclusive_access_selected_points_sale' => "Accès exclusif aux points de vente sélectionnés"
];
}


+ 1
- 0
domain/User/User/UserSearch.php View File

@@ -94,6 +94,7 @@ class UserSearch extends User
. '`user`.name_legal_person, '
. '`user`.type, '
. '`user`.problem_receiving_emails, '
. '`user`.note_emails, '
. '(SELECT COUNT(*) FROM `order` WHERE `user`.id = `order`.id_user) AS count_orders');

if($producer) {

+ 13
- 1
domain/User/UserProducer/UserProducer.php View File

@@ -63,7 +63,7 @@ class UserProducer extends ActiveRecordCommon
return [
[['id_user', 'id_producer'], 'required'],
[['id_user', 'id_producer', 'product_price_percent'], 'integer'],
[['active', 'bookmark', 'credit_active', 'newsletter', 'trust_alert'], 'boolean'],
[['active', 'bookmark', 'credit_active', 'newsletter', 'trust_alert', 'exclusive_access_selected_points_sale'], 'boolean'],
[['credit', 'product_price_percent'], 'double'],
[['trust_alert_comment'], 'string']
];
@@ -83,6 +83,7 @@ class UserProducer extends ActiveRecordCommon
'product_price_percent' => 'Prix produits : pourcentage',
'trust_alert' => 'Alert confiance',
'trust_alert_comment' => 'Commentaire',
'exclusive_access_selected_points_sale' => "Accès exclusif aux points de vente sélectionnés"
];
}

@@ -196,4 +197,15 @@ class UserProducer extends ActiveRecordCommon

return $this;
}

public function getExclusiveAccessSelectedPointsSale(): ?bool
{
return $this->exclusive_access_selected_points_sale;
}

public function setExclusiveAccessSelectedPointsSale(?bool $exclusiveAccessSelectedPointsSale): self
{
$this->exclusive_access_selected_points_sale = $exclusiveAccessSelectedPointsSale;
return $this;
}
}

+ 19
- 0
domain/User/UserProducer/UserProducerSolver.php View File

@@ -10,4 +10,23 @@ class UserProducerSolver extends AbstractSolver
{
return $userProducer->credit < 0 || $userProducer->credit > 0;
}

public function filterPointsSaleByExclusiveAccess(UserProducer $userProducer, array $pointsSaleArray = []): array
{
if(!$userProducer->getExclusiveAccessSelectedPointsSale()) {
return $pointsSaleArray;
}

$filteredPointsSaleArray = [];
foreach($pointsSaleArray as $pointSale) {
$idPointSale = is_array($pointSale) ? $pointSale['id'] : $pointSale->id;
foreach($userProducer->user->userPointSale as $userPointSale) {
if($idPointSale == $userPointSale->pointSale->id) {
$filteredPointsSaleArray[] = $pointSale;
}
}
}

return $filteredPointsSaleArray;
}
}

+ 9
- 11
frontend/views/site/_button_producer_signup.php View File

@@ -7,18 +7,16 @@ $userCurrent = $this->getUserCurrent();
?>

<?php if($producerManager->isProducerSignupOpen()): ?>
<?php if(Yii::$app->user->isGuest || $userModule->getSolver()->isStatusUser($userCurrent)): ?>
<a class="btn btn-primary"
href="<?= Yii::$app->user->isGuest ? \Yii::$app->urlManagerFrontend->createUrl(['site/signup', 'type' => 'producer']) : \Yii::$app->urlManagerFrontend->createUrl(['site/signup-producer']); ?>">
<i class="bi bi-shop"></i> Je crée mon espace producteur
</a><br />
<?php if($producerManager->getMaximumNumberProducers()): ?>
<strong><?= ($producerSignupRemainingPlaces); ?></strong>
place<?php if($producerSignupRemainingPlaces > 1): ?>s<?php endif; ?> restante<?php if($producerSignupRemainingPlaces > 1): ?>s<?php endif; ?>
<?php endif; ?>
<br />
<br />
<a class="btn btn-primary"
href="<?= Yii::$app->user->isGuest ? \Yii::$app->urlManagerFrontend->createUrl(['site/signup', 'type' => 'producer']) : \Yii::$app->urlManagerFrontend->createUrl(['site/signup-producer']); ?>">
<i class="bi bi-shop"></i> Je crée mon espace producteur
</a><br />
<?php if($producerManager->getMaximumNumberProducers()): ?>
<strong><?= ($producerSignupRemainingPlaces); ?></strong>
place<?php if($producerSignupRemainingPlaces > 1): ?>s<?php endif; ?> restante<?php if($producerSignupRemainingPlaces > 1): ?>s<?php endif; ?>
<?php endif; ?>
<br />
<br />
<?php else: ?>
<div class="label label-warning label-producer-signup-closed">
La plateforme n'accueille pas de nouveaux producteurs pour le moment

+ 4
- 4
frontend/views/site/_prices_producer.php View File

@@ -93,16 +93,16 @@ use yii\helpers\Html;

<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
<h3 class="panel-title" id="modules">
<i class="bi bi-boxes"></i>
Modules
</h3>
</div>
<div class="panel-body">
<div class="alert alert-dark" role="alert">
Retrouvez ici les modules payants de Souke correspondant aux développements qui n'ont pas encore
été totalement financés et aux fonctionnalités nécessitant une configuration spécifique. Contactez-moi
pour demander l'activation d'un module.
Retrouvez ici les modules payants de Souke correspondant à des fonctionnalités en lien avec des
plateformes externes ou à des développements répondant à des besoins spécifiques. N'hésitez pas à
<a href="<?= Yii::$app->urlManager->createUrl(['site/contact']) ?>">me contacter</a> pour en savoir plus ou pour demander l'activation d'un module.
</div>
<table class="table table-striped table-bordered">
<thead>

+ 0
- 5
frontend/views/site/iamproducer.php View File

@@ -44,11 +44,6 @@ $producerModule = $this->getProducerModule();
?>
<div class="site-iamproducer">
<div class="panel panel-primary panel-padding-large">
<!--<div class="panel-heading">
<h2 class="panel-title">
Vous avez besoin d'un outil pour vous organiser au quotidien ?
</h2>
</div>-->
<div class="panel-body">
<div class="hook">
Vous êtes producteur et vous souhaitez améliorer votre organisation au quotidien ?<br />

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

@@ -61,10 +61,10 @@ $this->setMeta('description', "Découvrez les fonctionnalités du logiciel, les
</div>
<div class="row">
<?= block_feature("calendar3", "Planification des jours de distributions"); ?>
<?= block_feature("download", "Récapitulatif des commandes par jour de distribution (PDF et CSV), génération d'étiquettes (PDF)"); ?>
<?= block_feature("download", "Récapitulatif des commandes par jour de distribution (PDF et CSV)<br />Génération d'étiquettes simple (PDF)<br />Génération d'étiquettes avec format spécifique (PDF) *"); ?>
</div>
<div class="row">
<?= block_feature("basket", "Gestion des produits, catégories et prix spécifiques"); ?>
<?= block_feature("basket", "Gestion des produits, catégories et prix spécifiques<br />Gestion des accessoires produits *"); ?>
<?php $featureSharedPointSaleDescription = '';
if($featureChecker->isEnabled(Feature::ALIAS_SHARED_POINT_SALE)):
$featureSharedPointSaleDescription .= "<br />Partage de points de vente entre plusieurs producteurs pour des distributions communes";
@@ -77,7 +77,7 @@ $this->setMeta('description', "Découvrez les fonctionnalités du logiciel, les
</div>
<div class="row">
<?= block_feature("piggy-bank", "Système de cagnotte permettant la comptabilisation des paiements"); ?>
<?= block_feature("credit-card", "Paiement en ligne possible via la plateforme <strong>Stripe</strong> pour que les clients puissent alimenter leur cagnotte de manière autonome"); ?>
<?= block_feature("credit-card", "Paiement en ligne via la plateforme <strong>Stripe</strong> pour le paiement des commandes ou l'alimentation des cagnottes *"); ?>
</div>
<div class="row">
<?= block_feature("megaphone", "Communication facilitée avec les clients via l'envoi d'emails en masse"); ?>
@@ -85,8 +85,12 @@ $this->setMeta('description', "Découvrez les fonctionnalités du logiciel, les
</div>
<div class="row">
<?= block_feature("graph-up", "Statistiques et rapports de vente"); ?>
<?= block_feature("cloud-arrow-up", "Exports vers les logiciels <strong>Evoliz</strong> (comptabilité) et <strong>SumUp</strong> (caisse)"); ?>
<?= block_feature("cloud-arrow-up", "Exports vers les logiciels <strong>Evoliz</strong> (comptabilité) et <strong>SumUp</strong> (caisse) *"); ?>
</div>

<p>
* <a href="#modules">Modules payants</a>
</p>
</div>
</div>


+ 45
- 28
producer/controllers/OrderController.php View File

@@ -170,10 +170,6 @@ class OrderController extends ProducerBaseController
$idProducer = $producer->id;
$order = new Order;

if ($idProducer) {
$this->_verifyProducerActive($idProducer);
}

if ($order->load($posts)) {
$user = $this->getUserCurrent();

@@ -445,8 +441,9 @@ class OrderController extends ProducerBaseController
) && isset($unitsArray[$product->id])) ? $unitsArray[$product->id] : $product->unit;
$coefficient = Product::$unitsArray[$unit]['coefficient'];
$quantity = ((float)$posts['products'][$product->id]) / $coefficient;
if ($availableProducts[$product->id]['quantity_max'] && $quantity > $availableProducts[$product->id]['quantity_remaining']) {
$quantity = $availableProducts[$product->id]['quantity_remaining'];
$quantityRemaining = $orderModule->getResolver()->getProductQuantityRemaining($product, $order->distribution);
if(!is_null($quantityRemaining) && $quantity > $quantityRemaining) {
$quantity = $quantityRemaining;
}
$productOrder->quantity = $quantity;
$productOrder->price = $productModule->getPrice($product, [
@@ -613,15 +610,16 @@ class OrderController extends ProducerBaseController
return 0;
}

public function actionAjaxInfos(string $date = '', int $pointSaleId = 0)
public function actionAjaxInfos(string $date = '', int $pointSaleId = 0, string $productsJson = null, bool $loadingProducts = false)
{
\Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
$productsArray = json_decode($productsJson, true);
$user = GlobalParam::getCurrentUser();
$producer = $this->getProducerCurrent();
$pointSale = $this->getPointSaleModule()->findOnePointSaleById($pointSaleId);
$order = $this->getOrderUser($date, $pointSale);

return $this->buildJsonAjaxInfos($date, $producer, $pointSale, $user, $order);
return $this->buildJsonAjaxInfos($date, $producer, $pointSale, $user, $order, $productsArray, $loadingProducts);
}
public function buildJsonAjaxInfos(
@@ -629,7 +627,9 @@ class OrderController extends ProducerBaseController
Producer $producer,
PointSale $pointSale = null,
User $user = null,
Order $order = null
Order $order = null,
array $productsArray = null,
bool $loadingProducts = false
)
{
$orderModule = $this->getOrderModule();
@@ -649,7 +649,7 @@ class OrderController extends ProducerBaseController
$json['distribution'] = $distribution;
$json['points_sale'] = $this->ajaxInfosPointsSale($producer, $distribution);
$json['categories'] = $this->ajaxInfosProductCategories($producer);
$json['products'] = $this->ajaxInfosProducts($producer, $distribution, $pointSale, $user, $order);
$json['products'] = $this->ajaxInfosProducts($producer, $distribution, $pointSale, $user, $order, $productsArray);

if ($order) {
$json['order'] = array_merge($order->getAttributes(), [
@@ -817,6 +817,7 @@ class OrderController extends ProducerBaseController
$orderModule = $this->getOrderModule();

$user = GlobalParam::getCurrentUser();
$userProducer = $this->getUserProducerModule()->getRepository()->findOneUserProducer($user);
$pointsSaleArray = PointSale::find();

if ($distribution) {
@@ -911,6 +912,12 @@ class OrderController extends ProducerBaseController
}
}

// Filtre par accès exclusif aux points de vente
$pointsSaleArray = $this->getUserProducerModule()->getSolver()->filterPointsSaleByExclusiveAccess(
$userProducer,
$pointsSaleArray
);

return $pointsSaleArray;
}

@@ -931,7 +938,8 @@ class OrderController extends ProducerBaseController
Distribution $distribution,
PointSale $pointSale = null,
User $user = null,
Order $order = null
Order $order = null,
array $productsFormArray = null
)
{
$unitModule = $this->getUnitModule();
@@ -960,6 +968,20 @@ class OrderController extends ProducerBaseController

$productsArrayFilter = $productModule->filterProductsByPointSale($productsArray, $pointSale);

$orderCurrent = $order;
if(count($productsFormArray)) {
$productOrdersArray = [];
foreach($productsFormArray as $idProduct => $quantityProduct) {
if($idProduct) {
$productObject1 = $productModule->getRepository()->findOneProductById($idProduct);
$productOrdersArray[$idProduct] = [
'quantity' => $quantityProduct / $productModule->getSolver()->getUnitCoefficient($productObject1)
];
}
}
$orderCurrent = $orderModule->getBuilder()->instanciateOrderFromProductOrdersArray($productOrdersArray, $orderCurrent);
}

$indexProduct = 0;
foreach ($productsArrayFilter as $key => &$product) {
$productObject = $product;
@@ -974,8 +996,6 @@ class OrderController extends ProducerBaseController
]
);

$coefficient_unit = Product::$unitsArray[$product['unit']]['coefficient'];

if (is_null($product['photo']) || strlen($product['photo']) == 0) {
$product['photo'] = '';
}
@@ -984,33 +1004,30 @@ class OrderController extends ProducerBaseController
$product['photo'] = Image::getThumbnailSmall($product['photo']);
}

$product['quantity_max'] = (isset($product['productDistribution']) && isset($product['productDistribution'][0])) ? $product['productDistribution'][0]['quantity_max'] : null;
$quantityOrder = $orderModule->getProductQuantity($productObject, $ordersArray);
$product['quantity_ordered'] = $quantityOrder;
$product['quantity_remaining'] = $product['quantity_max'] - $quantityOrder;
$coefficientUnit = Product::$unitsArray[$product['unit']]['coefficient'];
$product['coefficient_unit'] = $coefficientUnit;
$product['quantity_max'] = $orderModule->getResolver()->getProductQuantityMaxOrderable($productObject, $distribution, $orderCurrent);
$product['quantity_remaining'] = $orderModule->getResolver()->getProductQuantityRemaining($productObject, $distribution, $orderCurrent);
$product['wording_unit'] = $unitModule->getSolver()->strUnit($product['unit'], UnitDefinition::WORDING_UNIT, true);
$product['wording_unit_ref'] = $unitModule->getSolver()->strUnit($product['unit'], UnitDefinition::WORDING_SHORT, true);
$quantityOrderUser = $orderModule->getSolver()->getProductQuantity($productObject, $orderCurrent ? [$orderCurrent] : [], true);
$product['quantity_form'] = $quantityOrderUser * $coefficientUnit;
if($product['quantity_remaining'] < 0 && $product['quantity_form']) {
$product['quantity_form'] = $product['quantity_form'] + ($product['quantity_remaining'] * $coefficientUnit);
$orderModule->getBuilder()->deleteProductOrderQuantity($orderCurrent, $productObject, abs($product['quantity_remaining']));
$product['quantity_remaining'] = $orderModule->getResolver()->getProductQuantityRemaining($productObject, $distribution, $orderCurrent);
}
$product['wording_unit'] = $unitModule->getSolver()->strUnit($product['unit'], 'wording_unit', true);

if ($order) {
$quantityOrderUser = $orderModule->getProductQuantity($productObject, [$order], true);
$product['quantity_ordered'] = $quantityOrder;
$product['quantity_remaining'] = $product['quantity_max'] - $quantityOrder + $quantityOrderUser;
$product['quantity_form'] = $quantityOrderUser * $coefficient_unit;
foreach ($order->productOrder as $productOrder) {
if ($productOrder->id_product == $product['id']) {
$product['wording_unit'] = $productModule->getSolver()->strUnit($productOrder->product, 'wording_unit', true);
$product['step'] = $productOrder->step;
}
}
} else {
$product['quantity_form'] = 0;
$product['wording_unit'] = $unitModule->getSolver()->strUnit($product['unit'], 'wording_unit', true);
}
$product['coefficient_unit'] = $coefficient_unit;

if ($product['quantity_remaining'] < 0) {
$product['quantity_remaining'] = 0;
}
$product['index'] = $indexProduct++;
}


+ 0
- 341
producer/views/order/_form.php View File

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

/**
Copyright Souke (2018)

contact@souke.fr

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\helpers\GlobalParam;
use common\helpers\Price;
use domain\Order\Order\OrderModule;
use domain\Producer\Producer\Producer;
use domain\Producer\Producer\ProducerModule;
use yii\helpers\Html;
use yii\widgets\ActiveForm;

$producerModule = ProducerModule::getInstance();
$orderModule = OrderModule::getInstance();

?>
<div class="order-form">
<?php
$form = ActiveForm::begin([
'enableClientScript' => false
]);
?>
<?php
if(count($distributionDaysArray) <= 1) :
?>
<div class="alert alert-warning">Aucun jour de production n'a été programmé par le producteur.</div>
<?php endif; ?>
<?php if($idProducer && count($distributionDaysArray) > 1): ?>
<div id="step-date" class="col-md-6">
<?= $form->field($model, 'id_distribution')->label('')->hiddenInput(); ?>

<?php if (isset($model->id)): ?>
<div class="date-order"><span><?php echo date('d/m/Y', strtotime($distribution->date)); ?></span></div>
<?= Html::hiddenInput('id_order', $model->id,['id'=>'id-order']); ?>
<?= Html::hiddenInput('paid_amount', $orderModule->getOrderAmount($model, Order::AMOUNT_PAID),['id'=>'paid-amount']); ?>
<?php endif; ?>

<div id="datepicker-distribution" <?php if (isset($model->id)): ?>style="display:none"<?php endif; ?>>
</div>

<?php if (!isset($model->id)): ?>
<br />
<?php endif; ?>

<div id="dates" style="display:none;">
<?php
foreach ($distributionDaysArray as $idDistribution => $day) {
if ($day != '--') {
echo '<div><span class="date">' . $day . '</span><span class="id_distribution">' . $idDistribution. '</span></div>';
}
}
?>
</div>

<div class="clr"></div>
<div id="orders-in-progress" style="display:none;">
<?php foreach ($ordersArray as $order): ?>
<?php echo '<div class="order" data-iddistribution="' . $order->id_distribution. '" data-id="' . $order->id . '" data-href="' . \Yii::$app->urlManager->createUrl(['order/update', 'id' => $order->id, 'id_producer' => $order->distribution->id_producer]) . '"></div>'; ?>
<?php endforeach; ?>
</div>
<div id="has-order-in-progress" style="display:none;" class="alert alert-danger">Vous avez déjà une commande en cours pour cette date. <a href="#">Cliquez ici</a> pour la modifier.</div>

</div>
<div class="col-md-6">
<?php if(strlen($producer->order_infos)): ?>
<div id="order-infos">
<?= nl2br(Html::encode($producer->order_infos)) ?>
</div>
<?php endif; ?>
</div>

<div class="clr"></div>

<div id="block-points-sale">
<h3 id="step-point-sale"><span><?= $producerModule->getPointSaleWording($producer); ?></span></h3>
<?=
$form->field($model, 'id_point_sale')
->label('')
->hiddenInput();
?>

<input type="hidden" id="livraison" value="<?php if (!is_null($distribution) && $distribution->delivery): ?>1<?php else: ?>0<?php endif; ?>" />

<ul id="points-sale" class="blocks">
<?php
foreach ($pointsSaleArray as $pointSale) {
$comment = '' ;
if(isset($pointSale->userPointSale) && is_array($pointSale->userPointSale) && count($pointSale->userPointSale))
{
foreach($pointSale->userPointSale as $userPointSale)
{
if($userPointSale->id_user == GlobalParam::getCurrentUserId() && strlen($userPointSale->comment))
{
$comment = '<div class="comment"><span>'.Html::encode($userPointSale->comment).'</span></div>' ;
}
}
}
$htmlCode = '' ;
$dataCode = '0' ;
$code = '' ;
if(strlen($pointSale->code))
{
if(!isset($model->id_point_sale) || $model->id_point_sale != $pointSale->id)
{
$htmlCode .= '<span class="glyphicon glyphicon-lock"></span> ' ;
$dataCode = '1' ;
}
else {
$code = $pointSale->code ;
}
}
echo '<li class="block point-sale point-sale-' . $pointSale->id . '" data-code="'.$dataCode.'" data-credit="'.(int) $pointSale->payment_method_credit.'"><div class="contenu">' .
'<span style="display:none;" class="id">' . $pointSale->id . '</span>' .
'<div class="name">' .$htmlCode. Html::encode($pointSale->name) . '</div>' .
'<div class="address">à ' . Html::encode($pointSale->locality) . '</div>' .
$comment .
'<input type="hidden" name="code_point_sale_'.$pointSale->id.'" value="'.$code.'" />'.
'</div></li>';
}
?>
</ul>
<div class="clr"></div>
<div id="step-infos-point-sale">
<?php
foreach ($pointsSaleArray as $pointSale) {
echo '<div class="alert alert-warning infos-point-sale infos-point-sale-'.$pointSale->id.'"><h4>Infos : <span>'.Html::encode($pointSale->name).'</span></h4>' .
'<div class="jour jour-1">' . $pointSale->getStrInfos('monday') . '</div>' .
'<div class="jour jour-2">' . $pointSale->getStrInfos('tuesday') . '</div>' .
'<div class="jour jour-3">' . $pointSale->getStrInfos('wednesday') . '</div>' .
'<div class="jour jour-4">' . $pointSale->getStrInfos('thursday') . '</div>' .
'<div class="jour jour-5">' . $pointSale->getStrInfos('friday') . '</div>' .
'<div class="jour jour-6">' . $pointSale->getStrInfos('saturday') . '</div>' .
'<div class="jour jour-0">' . $pointSale->getStrInfos('sunday') . '</div>' .
'</div>' ;
}
?>
</div>
<div class="clr"></div>
</div>
<div id="products">
<h3 id="step-products"><span>Produits</span></h3>

<?php // erreur ?>
<?php if (Yii::$app->session->getFlash('error')): ?>
<div class="alert alert-danger"><div class="icon"></div><?= \Yii::$app->session->getFlash('error'); ?></div>
<?php endif; ?>

<div id="">
<div class="alert alert-warning unavailable">Produit indisponible pour ce point de vente</div>
<table class="table table-bordered" id="table-products">
<thead>
<tr>
<th class="th-photo">Photo</th>
<th class="product">Produit</th>
<th class="price-unit">Prix unitaire</th>
<th class="column-quantity">Quantité</th>
<th class="total">Total</th>
</tr>
</thead>
<tbody>
<?php foreach ($productsArray as $product): ?>
<?php
$quantity = 0;
if (isset($selectedProducts[$product->id])) {
$quantity = $selectedProducts[$product->id];
}
?>
<tr class="product-<?php echo $product->id; ?>" data-no-limit="<?php if(!$product->quantity_max): ?>1<?php else: ?>0<?php endif; ?>" data-quantity-max="<?= $quantity ?>" <?php if (count($availableProducts) && !$availableProducts[$product->id]['active']): ?>style="display:none;"<?php endif; ?>>
<td class="td-photo">
<?php if (strlen($product->photo) && file_exists(dirname(__FILE__).'/../../web/uploads/' . $product->photo)): ?><a href="<?= \Yii::$app->urlManager->getBaseUrl() . '/uploads/' . $product->photo ?>" data-lightbox="product-<?php echo $product->id; ?>"><img class="photo img-rounded" src="<?= \Yii::$app->urlManager->getBaseUrl() . '/uploads/' . $product->photo ?>" alt="Photo <?= Html::encode($product->name); ?>" /></a><?php endif; ?>
</td>
<td class="produit">
<span class="name"><?= Html::encode($product->name); ?></span> - <span class="description"><?= Html::encode($product->getDescription()); ?></span><br />
<span class="recipe"><?= Html::encode($product->recipe); ?></span>
</td>
<td class="price-unit">
<span class="price"><?= Price::format($product->price); ?></span> €
</td>
<td class="column-quantity">
<div class="input-group" <?php if (isset($availableProducts[$product->id]) && $availableProducts[$product->id]['quantity_remaining'] == 0 && $quantity == 0): ?>style="display:none;"<?php endif; ?>>
<span class="input-group-btn">
<button type="button" class="btn btn-default move-quantity minus">-</button>
</span>
<input type="text" value="<?php if (isset($selectedProducts[$product->id])): echo $selectedProducts[$product->id];
else: ?>0<?php endif; ?>" readonly name="Product[product_<?php echo $product->id; ?>]" class="quantity form-control">
<span class="input-group-btn">
<button type="button" class="btn btn-default move-quantity plus">+</button>
</span>
</div>

<div class="quantity-remaining">Reste <span class="nb"><?php if (isset($availableProducts[$product->id])): echo $availableProducts[$product->id]['quantity_remaining'] + $quantity;
endif; ?></span> <?php echo Html::encode(strtolower($product->name)); ?>(s)
</div>
<div class="unavailable">Épuisé</div>
</td>
<td class="total"><strong></strong></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<tr>
<td></td>
<td></td>
<td></td>
<td id="total-order"><strong></strong></td>
</tr>
</tfoot>
</table>
</div>

</div>

<?php if($idProducer): ?>
<?php
$producer = Producer::findOne($idProducer);
?>
<div id="bar-fixed" class="<?php if($producer->credit): ?>credit<?php else: ?>no-credit<?php endif; ?>">
<div class="container">
<?php if (isset($model->id) && $orderModule->getSolver()->isOrderStatusValid($model)): ?>
<a href="<?php echo \Yii::$app->urlManager->createUrl(['order/cancel', 'id' => $model->id]); ?>" class="btn btn-danger cancel-order">Annuler ma commande</a>
<?php endif; ?>
<span id="total-order-bottom"><span></span> €</span>
<?= Html::submitButton('<span class="glyphicon glyphicon-comment"></span> Commentaire', ['class' => 'btn btn-default btn-comment', 'data-placement' => 'top', 'data-toggle' => 'tooltip', 'data-original-title' => 'Ajouter un commentaire']) ?>
<?php
if($producer->credit):
$linkCredit = '<a class="info-credit" href="'.Yii::$app->urlManager->createUrl(['site/credit']) .'" data-toggle="tooltip" data-placement="bottom" title="En savoir plus sur le Crédit"><span class="glyphicon glyphicon-info-sign"></span></a>' ; ;
?>
<div id="checkbox-credit" >
<?php if($credit || $orderModule->getAmount($order, Order::AMOUNT_PAID)): ?>
<?= Html::checkbox('credit', true, ['label' => 'Utiliser mon Crédit <span class="the-credit" data-toggle="tooltip" data-placement="top" data-original-title="Vous avez actuellement '.number_format($credit,2).' € sur votre compte Crédit">'.number_format($credit,2).'&nbsp;€</span><br /><span class="info"></span>']) ?>
<?= Html::hiddenInput('amount_credit', $credit, ['id' => 'amount-credit']) ?>
<?= Html::hiddenInput('str_amount_credit', number_format($credit,2).' €', ['id' => 'str-amount-credit']) ?>
<?php else: ?>
<div id="info-credit-empty">
Votre compte Crédit est vide <?= $linkCredit ?>
</div>
<?php endif; ?>
<div id="credit-disabled">Le Crédit est désactivé<br /> pour ce point de vente <?= $linkCredit ?></div>
</div>
<div class="clr"></div>
<?php endif; ?>
<?= $form->field($model, 'comment')->textarea(['rows' => 3, 'placeholder' => 'Un commentaire ?'])->label(''); ?>
<div id="block-confirm-order">
<?php if($model->date_delete): $strButtonConfirmOrder = 'Réactiver ma commande' ; else: $strButtonConfirmOrder = 'Valider ma commande' ; endif; ?>
<?= Html::submitButton('<span class="glyphicon glyphicon-ok"></span> '.$strButtonConfirmOrder, ['class' => 'btn btn-primary confirm-order']) ?>
</div>
<?php endif; ?>
</div>
</div>
<?php
// id_etablissement
endif; ?>
<?php ActiveForm::end(); ?>
<!-- modal code point de vente -->
<div class="modal fade" id="modal-code" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="myModalLabel">Code d'accès</h4>
</div>
<div class="modal-body">
<div class="alert alert-warning">
Ce point de vente nécessite un code d'accès.
</div>
<form action="index.php?r=order/validate-code-point-sale" method="post">
<input type="hidden" value="" name="idPointSale" id="id-point-sale" />
<div class="form-group field-code required">
<label class="control-label" for="code">Code d'accès :</label>
<input type="password" class="form-control" id="code" name="code" />
<p class="help-block help-block-error" style="display:none;">Code incorrect</p>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Valider</button>
</div>
</form>
</div>
</div>
</div>
</div>

</div><!-- commande-form -->

+ 3
- 3
producer/views/order/order.php View File

@@ -354,7 +354,7 @@ $this->setMeta('description', $producerModule->getSeoGenerator()->generateMetaDe
<span v-if="product.weight">({{ product.weight }}&nbsp;g)</span>
</span>
<div>
<span v-if="product.quantity_max > 0 && ((product.quantity_form / product.coefficient_unit == product.quantity_remaining) || ((product.quantity_remaining * product.coefficient_unit) - product.quantity_form) < product.step)"
<span v-if="product.quantity_max != null && (product.quantity_remaining <= 0 || product.quantity_remaining * product.coefficient_unit < product.step)"
class="badge bg-danger">Épuisé</span>
</div>
<div class="description" v-if="product.description.length">
@@ -402,7 +402,7 @@ $this->setMeta('description', $producerModule->getSeoGenerator()->generateMetaDe
<button class="btn btn-secondary btn-moins"
type="button"
@click="productQuantityClick(product, product.unit == 'piece' ? -1 : -parseFloat(product.step))"
:disabled="product.quantity_form == 0">
:disabled="product.quantity_form == 0 || loadingProducts">
<i class="bi bi-dash-lg"></i></button>
</span>
<input type="text" v-model="product.quantity_form"
@@ -412,7 +412,7 @@ $this->setMeta('description', $producerModule->getSeoGenerator()->generateMetaDe
<button class="btn btn-secondary btn-plus"
type="button"
@click="productQuantityClick(product, product.unit == 'piece' ? 1 : parseFloat(product.step))"
:disabled="product.quantity_form == product.quantity_remaining && product.quantity_max > 0">
:disabled="loadingProducts || (product.quantity_remaining != null && product.quantity_remaining <= 0) || (product.quantity_max != null && product.quantity_form >= product.quantity_max * product.coefficient_unit)">
<i class="bi bi-plus-lg"></i></button>
</span>
</div>

+ 214
- 174
producer/web/js/vuejs/order-order.js View File

@@ -5,8 +5,12 @@ var app = new Vue({
data() {
return Object.assign({
order: null,
timerAjaxInfosProducts: false,
xhr: false,
loading: false,
loadingProducts: false,
loadingInit: true,
cancelTokenSource: null,
step: null,
producer: null,
user: null,
@@ -75,7 +79,7 @@ var app = new Vue({
},
mounted: function() {
this.initDate();
this.init('first');
this.init('first', false);
},
methods: {
initDate: function() {
@@ -126,208 +130,239 @@ var app = new Vue({
}
}
},
init: function(type, oldStep, step) {

init: function(type, loadingProducts) {
var app = this ;
this.loading = true ;

if(loadingProducts) {
if (app.timerAjaxInfosProducts) {
clearTimeout(app.timerAjaxInfosProducts);
}
app.timerAjaxInfosProducts = setTimeout(function(app) {
app.loadingProducts = true ;
if(app.xhr) {
app.xhr.abort();
}
app.xhr = $.get('ajax-infos', {
date : app.getDate(),
pointSaleId: app.pointSaleActiveId ? app.pointSaleActiveId : (app.pointSaleActive ? app.pointSaleActive.id : 0),
productsJson: JSON.stringify(app.getProductsArray()),
loadingProducts: loadingProducts
}, function(response) {
app.products = response.products;
app.loadingProducts = false;
}, 'json');
}.bind(this, app), 300);

return;
}
else {
this.loading = true ;
}

if(app.isChangeState('date', 'date', 'point-sale')) {
app.pointSaleActive = null ;
app.products = [] ;
}

axios.get("ajax-infos",{params: {
date : this.getDate(),
pointSaleId: this.pointSaleActiveId ? this.pointSaleActiveId : (this.pointSaleActive ? this.pointSaleActive.id : 0)
//app.cancelTokenSource = axios.CancelToken.source();
axios.get("ajax-infos",{
//cancelToken: app.cancelTokenSource.token,
params: {
date : this.getDate(),
pointSaleId: this.pointSaleActiveId ? this.pointSaleActiveId : (this.pointSaleActive ? this.pointSaleActive.id : 0),
productsJson: this.getProductsArray(),
loadingProducts: loadingProducts
}})
.catch(function (thrown) {
if (axios.isCancel(thrown)) {
}
})
.then(function(response) {
app.calendar.attrs = [];
app.calendar.availableDates = [];

var distributions = response.data.distributions;
app.distributions = distributions;
if (distributions.length) {
var arrayDate;
var highlightStyle = {
style: {
background: 'white',
border: 'solid 2px #198754'
},
contentStyle: {
color: '#198754'
}
};
for (var i = 0; i < distributions.length; i++) {
app.calendar.attrs.push({
highlight: highlightStyle,
dates: distributions[i].date
});
if(response) {
app.calendar.attrs = [];
app.calendar.availableDates = [];

var distributions = response.data.distributions;
app.distributions = distributions;
if (distributions.length) {
var arrayDate;
var highlightStyle = {
style: {
background: 'white',
border: 'solid 2px #198754'
},
contentStyle: {
color: '#198754'
}
};
for (var i = 0; i < distributions.length; i++) {
app.calendar.attrs.push({
highlight: highlightStyle,
dates: distributions[i].date
});

arrayDate = distributions[i].date.split('-');
app.calendar.availableDates.push({
highlight: highlightStyle,
start: new Date(arrayDate[0], arrayDate[1] - 1, arrayDate[2]),
end: new Date(arrayDate[0], arrayDate[1] - 1, arrayDate[2])
});
arrayDate = distributions[i].date.split('-');
app.calendar.availableDates.push({
highlight: highlightStyle,
start: new Date(arrayDate[0], arrayDate[1] - 1, arrayDate[2]),
end: new Date(arrayDate[0], arrayDate[1] - 1, arrayDate[2])
});
}
}
}

if(response.data.leave_period) {
leavePeriodStartDateArray = response.data.leave_period.start.split('-');
leavePeriodEndDateArray = response.data.leave_period.end.split('-');
if (response.data.leave_period) {
leavePeriodStartDateArray = response.data.leave_period.start.split('-');
leavePeriodEndDateArray = response.data.leave_period.end.split('-');

app.calendar.attrs.push({
highlight: {
style: {
//background: '#E09F3E'
background: 'gray'
app.calendar.attrs.push({
highlight: {
style: {
//background: '#E09F3E'
background: 'gray'
},
contentStyle: {
color: 'white'
}
},
contentStyle: {
color: 'white'
}
},
dates: {
start: new Date(leavePeriodStartDateArray[0], leavePeriodStartDateArray[1] - 1, leavePeriodStartDateArray[2]),
end: new Date(leavePeriodEndDateArray[0], leavePeriodEndDateArray[1] - 1, leavePeriodEndDateArray[2])
},
popover: {
label: 'En congé',
hideIndicator: true,
isInteractive: true
},
});
}
dates: {
start: new Date(leavePeriodStartDateArray[0], leavePeriodStartDateArray[1] - 1, leavePeriodStartDateArray[2]),
end: new Date(leavePeriodEndDateArray[0], leavePeriodEndDateArray[1] - 1, leavePeriodEndDateArray[2])
},
popover: {
label: 'En congé',
hideIndicator: true,
isInteractive: true
},
});
}

if (response.data.distribution) {
app.distribution = response.data.distribution;
}
if (response.data.distribution) {
app.distribution = response.data.distribution;
}

var orders = [];
if (response.data.orders) {
orders = response.data.orders;
}
var orders = [];
if (response.data.orders) {
orders = response.data.orders;
}

if (orders.length) {
for (var i = 0; i < orders.length; i++) {
arrayDate = orders[i].date_distribution.split('-');
var dateOrder = new Date(arrayDate[0], arrayDate[1] - 1, arrayDate[2]);
if (app.isAvailableDate(dateOrder)) {
app.calendar.attrs.push({
highlight: {
style: {
background: '#198754'
if (orders.length) {
for (var i = 0; i < orders.length; i++) {
arrayDate = orders[i].date_distribution.split('-');
var dateOrder = new Date(arrayDate[0], arrayDate[1] - 1, arrayDate[2]);
if (app.isAvailableDate(dateOrder)) {
app.calendar.attrs.push({
highlight: {
style: {
background: '#198754'
},
contentStyle: {
color: 'white'
}
},
contentStyle: {
color: 'white'
}
},
popover: {
label: orders[i].pointSale.name + ' (' + app.formatPrice(orders[i].amount_total)+')',
hideIndicator: true,
isInteractive: true
},
dates: orders[i].date_distribution
});
popover: {
label: orders[i].pointSale.name + ' (' + app.formatPrice(orders[i].amount_total) + ')',
hideIndicator: true,
isInteractive: true
},
dates: orders[i].date_distribution
});
}
}
}
}

app.producer = response.data.producer;
app.user = response.data.user;
app.useCredit = response.data.producer.use_credit_checked_default;
app.producer = response.data.producer;
app.user = response.data.user;
app.useCredit = response.data.producer.use_credit_checked_default;


if (response.data.points_sale) {
app.pointsSale = [];
var orderPointSale = 0;
for (var key in response.data.points_sale) {
response.data.points_sale[key].order = orderPointSale++;
app.pointsSale[response.data.points_sale[key].id] = response.data.points_sale[key];
if (response.data.points_sale) {
app.pointsSale = [];
var orderPointSale = 0;
for (var key in response.data.points_sale) {
response.data.points_sale[key].order = orderPointSale++;
app.pointsSale[response.data.points_sale[key].id] = response.data.points_sale[key];

if(!app.pointsSaleCodes[response.data.points_sale[key].id]) {
app.pointsSaleCodes[response.data.points_sale[key].id] = '';
Vue.set(app.pointsSaleCodes, response.data.points_sale[key].id, '');
if (!app.pointsSaleCodes[response.data.points_sale[key].id]) {
app.pointsSaleCodes[response.data.points_sale[key].id] = '';
Vue.set(app.pointsSaleCodes, response.data.points_sale[key].id, '');
}
}
}
}

if(app.pointSaleActiveId) {
app.pointSaleActive = app.getPointSale(app.pointSaleActiveId);
}

if(app.pointSaleActive) {
if(app.producer.credit
&& app.pointSaleActive.payment_method_credit
&& (app.pointSaleActive.credit_functioning == 'mandatory'
|| (app.pointSaleActive.credit_functioning == 'user' && app.user.credit_active)
|| (app.pointSaleActive.credit_functioning == 'optional' && response.data.producer.use_credit_checked_default))) {
app.paymentMethod = 'credit';
if (app.pointSaleActiveId) {
app.pointSaleActive = app.getPointSale(app.pointSaleActiveId);
}
else if(app.pointSaleActive.payment_method_onsite) {
app.paymentMethod = 'onsite';

if (app.pointSaleActive) {
if (app.producer.credit
&& app.pointSaleActive.payment_method_credit
&& (app.pointSaleActive.credit_functioning == 'mandatory'
|| (app.pointSaleActive.credit_functioning == 'user' && app.user.credit_active)
|| (app.pointSaleActive.credit_functioning == 'optional' && response.data.producer.use_credit_checked_default))) {
app.paymentMethod = 'credit';
} else if (app.pointSaleActive.payment_method_onsite) {
app.paymentMethod = 'onsite';
} else if (app.pointSaleActive.payment_method_online) {
app.paymentMethod = 'online';
}
}
else if(app.pointSaleActive.payment_method_online) {
app.paymentMethod = 'online';

if (app.isChangeState('point-sale', 'point-sale', 'date')) {
app.date = null;
app.dateFormat = null;
}
}

if(app.isChangeState('point-sale', 'point-sale', 'date')) {
app.date = null ;
app.dateFormat = null ;
}
// update order
var updateOrder = false;
if (app.isChangeState('date', 'point-sale', 'products')
|| app.isChangeState('date', 'date', 'point-sale')
|| app.isChangeState('point-sale', 'date', 'products')
|| app.isChangeState('point-sale', 'point-sale', 'date')) {

// update order
var updateOrder = false ;
if(app.isChangeState('date', 'point-sale', 'products')
|| app.isChangeState('date', 'date', 'point-sale')
|| app.isChangeState('point-sale', 'date', 'products')
|| app.isChangeState('point-sale', 'point-sale', 'date')) {
updateOrder = true;
}

updateOrder = true ;
}
if (updateOrder) {
app.updateOrder(response);
}

if(updateOrder) {
app.updateOrder(response);
}
if (type == 'first') {
if (app.getDate() && app.pointSaleActive) {
app.step = 'products';
if (response.data.products) {
app.products = response.data.products;
}

if(type == 'first') {
if(app.getDate() && app.pointSaleActive) {
app.step = 'products' ;
if(response.data.products) {
app.products = response.data.products;
app.updateOrder(response);
} else if (app.producer.option_order_entry_point == 'point-sale') {
app.step = 'point-sale';
} else if (app.getDate() && app.producer && app.producer.option_order_entry_point == 'date') {
app.step = 'point-sale';
} else {
app.step = 'date';
}

app.updateOrder(response);
}
else if(app.producer.option_order_entry_point == 'point-sale') {
app.step = 'point-sale' ;
}
else if(app.getDate() && app.producer && app.producer.option_order_entry_point == 'date') {
app.step = 'point-sale' ;
}
else {
app.step = 'date' ;
}
}

if(response.data.categories) {
app.categories = response.data.categories ;
for(keyCategory in response.data.categories) {
var category = response.data.categories[keyCategory];
if(category.id && app.countProductsByCategory(category)) {
app.setCategoryCurrent(category, true) ;
break;
if (response.data.categories) {
app.categories = response.data.categories;
for (keyCategory in response.data.categories) {
var category = response.data.categories[keyCategory];
if (category.id && app.countProductsByCategory(category)) {
app.setCategoryCurrent(category, true);
break;
}
}
}
}

setTimeout(function() {
app.responsive();
opendistrib_products();
}, 500);
setTimeout(function () {
app.responsive();
opendistrib_products();
}, 500);

app.loading = false ;
app.loadingInit = false ;
app.loading = false;
app.loadingProducts = false;
app.loadingInit = false;
}
});
},
updateOrder: function(response) {
@@ -412,7 +447,6 @@ var app = new Vue({
}

},

changeStep: function(step) {
this.errors = [] ;
var oldStep = this.step ;
@@ -423,7 +457,7 @@ var app = new Vue({
}
if(!this.errors.length) {
this.step = step ;
this.init('basic', oldStep, step) ;
this.init('basic', false) ;
}
},
dayClickList: function(event) {
@@ -489,12 +523,15 @@ var app = new Vue({
this.nextStep() ;
},
productQuantityClick: function(product, quantity) {
if( this.products[product.index].quantity_form + quantity >= 0 &&
(this.products[product.index].quantity_form + quantity <= (this.products[product.index].quantity_remaining * this.products[product.index].coefficient_unit) ||
!this.products[product.index].quantity_max)
if(this.products[product.index].quantity_form + quantity >= 0
&& (this.products[product.index].quantity_max == null
|| this.products[product.index].quantity_form + quantity <= this.products[product.index].quantity_max * this.products[product.index].coefficient_unit)
&& (this.products[product.index].quantity_remaining == null
|| (quantity <= (this.products[product.index].quantity_remaining * this.products[product.index].coefficient_unit)))
) {
var theQuantity = parseFloat(this.products[product.index].quantity_form) + parseFloat(quantity);
this.products[product.index].quantity_form = parseFloat(theQuantity.toFixed(2)) ;
this.init('first', true);
}
},
oneProductOrdered: function() {
@@ -652,14 +689,7 @@ var app = new Vue({
}

// products
var productsArray = {} ;
for(var key in this.products) {
if( this.products[key].quantity_form != null &&
this.products[key].quantity_form > 0) {
productsArray[this.products[key].id] = this.products[key].quantity_form ;
}
}

var productsArray = this.getProductsArray();
app.disableConfirmButton = true ;

axios.post('ajax-process', {
@@ -691,6 +721,16 @@ var app = new Vue({
}
});
},
getProductsArray: function() {
var productsArray = {} ;
for(var key in this.products) {
if( this.products[key].quantity_form != null &&
this.products[key].quantity_form > 0) {
productsArray[this.products[key].id] = this.products[key].quantity_form ;
}
}
return productsArray;
},
checkProducts: function() {
if(!this.oneProductOrdered()) {
this.errors.push('Veuillez sélectionner au moins un produit.') ;

Loading…
Cancel
Save