Explorar el Código

Merge branch 'backend/feature/nouvelle_gestion_commandes' into dev

dev
Guillaume Bourgeois hace 5 años
padre
commit
70c4f38779
Se han modificado 28 ficheros con 13609 adiciones y 581 borrados
  1. +62
    -0
      backend/assets/VuejsDistributionIndexAsset.php
  2. +362
    -0
      backend/controllers/DistributionController.php
  3. +75
    -315
      backend/controllers/OrderController.php
  4. +482
    -0
      backend/views/distribution/index.php
  5. +207
    -0
      backend/views/distribution/report.php
  6. +2
    -2
      backend/views/layouts/left.php
  7. +2
    -1
      backend/views/layouts/main.php
  8. +1
    -1
      backend/views/site/index.php
  9. BIN
      backend/web/.sass-cache/2a0ffb00578c9d5a537db16d14c734a22b18f35c/screen.scssc
  10. +549
    -236
      backend/web/css/screen.css
  11. BIN
      backend/web/img/loader.gif
  12. +2
    -2
      backend/web/js/lechatdesnoisettes.js
  13. +402
    -0
      backend/web/js/vuejs/distribution-index.js
  14. +4
    -1
      backend/web/sass/_adminlte.scss
  15. +351
    -0
      backend/web/sass/distribution/_index.scss
  16. +3
    -14
      backend/web/sass/screen.scss
  17. +4
    -1
      common/assets/CommonAsset.php
  18. +4
    -0
      common/components/ActiveRecordCommon.php
  19. +1
    -1
      common/models/Distribution.php
  20. +2
    -2
      common/models/Order.php
  21. +3
    -3
      common/models/PointSaleDistribution.php
  22. +7
    -1
      common/models/Product.php
  23. +9
    -0
      common/web/js/axios/axios.min.js
  24. +3
    -0
      common/web/js/vuejs/vcalendar/vcalendar.min.css
  25. +2
    -0
      common/web/js/vuejs/vcalendar/vcalendar.min.js
  26. +11063
    -0
      common/web/js/vuejs/vue.js
  27. +6
    -0
      common/web/js/vuejs/vue.min.js
  28. +1
    -1
      producer/controllers/OrderController.php

+ 62
- 0
backend/assets/VuejsDistributionIndexAsset.php Ver fichero

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

contact@laboiteapain.net

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

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

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

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

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

namespace backend\assets;

use yii\web\AssetBundle;
use yii ;

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

+ 362
- 0
backend/controllers/DistributionController.php Ver fichero

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

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

contact@laboiteapain.net

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

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

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

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

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

namespace backend\controllers;

use backend\controllers\BackendController ;
use common\models\Distribution ;
use common\models\Product ;
use common\models\Producer ;
use common\models\Order ;
use common\models\User ;
use DateTime;

class DistributionController extends BackendController
{
public function actionIndex($date = '')
{
$format = 'Y-m-d' ;
$theDate = '' ;
$dateObject = DateTime::createFromFormat($format, $date);
if($dateObject && $dateObject->format($format) === $date) {
$theDate = $date ;
}
return $this->render('index', [
'date' => $theDate
]) ;
}
public function actionAjaxInfos($date = '')
{
\Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
$json = [
'distribution' => [],
'products' => []
] ;
$format = 'Y-m-d' ;
$dateObject = DateTime::createFromFormat($format, $date);
$distributionsArray = Distribution::searchAll([
'active' => 1
], [
'conditions' => ['date > :date_begin','date < :date_end'],
'params' => [':date_begin' => date('Y-m-d', strtotime('-1 month')), ':date_end' => date('Y-m-d', strtotime('+3 month')), ],
]) ;
$json['distributions'] = $distributionsArray ;
if($dateObject && $dateObject->format($format) === $date) {
// distribution
$distribution = Distribution::initDistribution($date) ;
$json['distribution'] = [
'id' => $distribution->id,
'active' => $distribution->active,
'url_report' => Yii::$app->urlManagerBackend->createUrl(['distribution/report','date' => $distribution->date])
] ;
// commandes
$ordersArray = Order::searchAll([
'distribution.date' => $date,
]);
// montant et poids des commandes
$revenues = 0;
$weight = 0 ;
if($ordersArray) {
foreach ($ordersArray as $order) {
if(is_null($order->date_delete)) {
$revenues += $order->amount;
$weight += $order->weight;
}
}
}
$json['distribution']['revenues'] = number_format($revenues, 2);
$json['distribution']['weight'] = number_format($weight, 2);
// products
$productsArray = Product::find()
->where([
'id_producer' => Producer::getId(),
])
->joinWith(['productDistribution' => function($query) use($distribution) {
$query->andOnCondition('product_distribution.id_distribution = '.$distribution->id) ;
}])
->orderBy('product_distribution.active DESC, order ASC')
->asArray()
->all();
$potentialRevenues = 0;
$potentialWeight = 0;
foreach($productsArray as &$product) {
$quantityOrder = Order::getProductQuantity($product['id'], $ordersArray) ;
$product['quantity_ordered'] = $quantityOrder ;
$product['quantity_remaining'] = $product['quantity_max'] - $quantityOrder ;
if($product['quantity_remaining'] < 0) $product['quantity_remaining'] = 0 ;
$product['quantity_form'] = 0 ;
if($product['productDistribution'][0]['active']) {
$potentialRevenues += $product['quantity_max'] * $product['price'];
$potentialWeight += $product['quantity_max'] * $product['weight'] / 1000;
}
}
$json['distribution']['potential_revenues'] = $potentialRevenues ;
$json['distribution']['potential_weight'] = $potentialWeight ;
$json['products'] = $productsArray ;
// orders as array
if($ordersArray) {
foreach($ordersArray as &$order) {
$productOrderArray = \yii\helpers\ArrayHelper::map($order->productOrder, 'id_product', 'quantity') ;
foreach($productsArray as $product) {
if(!isset($productOrderArray[$product['id']])) {
$productOrderArray[$product['id']] = 0 ;
}
}
$creditHistoryArray = [] ;
foreach($order->creditHistory as $creditHistory) {
$creditHistoryArray[] = [
'date' => date('d/m/Y H:i:s', strtotime($creditHistory->date)),
'user_action' => $creditHistory->strUserAction(),
'wording' => $creditHistory->getStrWording(),
'debit' => ($creditHistory->isTypeDebit() ? '- '.$creditHistory->getAmount(Order::AMOUNT_TOTAL,true) : ''),
'credit' => ($creditHistory->isTypeCredit() ? '+ '.$creditHistory->getAmount(Order::AMOUNT_TOTAL,true) : '')
] ;
}
$order = array_merge($order->getAttributes(), [
'amount' => $order->getAmount(Order::AMOUNT_TOTAL),
'amount_paid' => $order->getAmount(Order::AMOUNT_PAID),
'amount_remaining' => $order->getAmount(Order::AMOUNT_REMAINING),
'amount_surplus' => $order->getAmount(Order::AMOUNT_SURPLUS),
'user' => (isset($order->user)) ? $order->user->getAttributes() : null,
'pointSale' => ['id' => $order->pointSale->id, 'name' => $order->pointSale->name],
'productOrder' => $productOrderArray,
'creditHistory' => $creditHistoryArray
]) ;
}
}
$json['orders'] = $ordersArray ;
// order create
$productOrderArray = [] ;
foreach($productsArray as $product) {
$productOrderArray[$product['id']] = 0 ;
}
$json['order_create'] = [
'id_point_sale' => 0,
'id_user' => 0,
'username' => '',
'comment' => '',
'productOrder' => $productOrderArray
] ;
// points de vente
$pointsSaleArray = PointSale::find()
->joinWith(['pointSaleDistribution' => function($q) use ($distribution) {
$q->where(['id_distribution' => $distribution->id]);
}])
->where([
'id_producer' => Producer::getId(),
])
->asArray()
->all();
$json['points_sale'] = $pointsSaleArray ;
// utilisateurs
$usersArray = User::findBy()->all() ;
$json['users'] = $usersArray ;
}
return $json ;
}
/**
* Génére un PDF récapitulatif des commandes d'un producteur pour une
* date donnée.
*
* @param string $date
* @param boolean $save
* @param integer $idProducer
* @return PDF|null
*/
public function actionReport($date = '', $save = false, $idProducer = 0)
{
if (!Yii::$app->user->isGuest) {
$idProducer = Producer::getId() ;
}
$ordersArray = Order::searchAll([
'distribution.date' => $date,
],
[
'orderby' => 'comment_point_sale ASC, user.name ASC',
'conditions' => 'date_delete IS NULL'
]) ;
$distribution = Distribution::searchOne([],[
'conditions' => 'date LIKE :date',
'params' => [':date' => $date]
]) ;
if ($distribution) {
$selectedProductsArray = ProductDistribution::searchByDistribution($distribution->id) ;
$pointsSaleArray = PointSale::searchAll() ;
foreach ($pointsSaleArray as $pointSale) {
$pointSale->initOrders($ordersArray) ;
}

// produits
$productsArray = Product::searchAll() ;

// get your HTML raw content without any layouts or scripts
$content = $this->renderPartial('report', [
'date' => $date,
'distribution' => $distribution,
'selectedProductsArray' => $selectedProductsArray,
'pointsSaleArray' => $pointsSaleArray,
'productsArray' => $productsArray,
'ordersArray' => $ordersArray
]);

$dateStr = date('d/m/Y', strtotime($date));

if ($save) {
$destination = Pdf::DEST_FILE;
} else {
$destination = Pdf::DEST_BROWSER;
}

$pdf = new Pdf([
// set to use core fonts only
'mode' => Pdf::MODE_UTF8,
// A4 paper format
'format' => Pdf::FORMAT_A4,
// portrait orientation
'orientation' => Pdf::ORIENT_PORTRAIT,
// stream to browser inline
'destination' => $destination,
'filename' => Yii::getAlias('@app/web/pdf/Commandes-' . $date . '-' . $idProducer . '.pdf'),
// your html content input
'content' => $content,
// format content from your own css file if needed or use the
// enhanced bootstrap css built by Krajee for mPDF formatting
//'cssFile' => '@vendor/kartik-v/yii2-mpdf/assets/kv-mpdf-bootstrap.min.css',
// any css to be embedded if required
//'cssInline' => '.kv-heading-1{font-size:18px}',
// set mPDF properties on the fly
//'options' => ['title' => 'Krajee Report Title'],
// call mPDF methods on the fly
'methods' => [
'SetHeader' => ['Commandes du ' . $dateStr],
'SetFooter' => ['{PAGENO}'],
]
]);

// return the pdf output as per the destination setting
return $pdf->render();
}
return null ;
}
public function actionAjaxProcessProductQuantityMax($idDistribution, $idProduct, $quantityMax)
{
\Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
$productDistribution = ProductDistribution::searchOne([
'id_distribution' => $idDistribution,
'id_product' => $idProduct,
]) ;
$productDistribution->quantity_max = (int) $quantityMax ;
$productDistribution->save() ;
return ['success'] ;
}
public function actionAjaxProcessActiveProduct($idDistribution, $idProduct, $active)
{
\Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
$productDistribution = ProductDistribution::searchOne([
'id_distribution' => $idDistribution,
'id_product' => $idProduct,
]) ;
$productDistribution->active = $active ;
$productDistribution->save() ;
return ['success'] ;
}
public function actionAjaxProcessActivePointSale($idDistribution, $idPointSale, $delivery)
{
\Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
$pointSaleDistribution = PointSaleDistribution::searchOne([
'id_distribution' => $idDistribution,
'id_point_sale' => $idPointSale,
]) ;
$pointSaleDistribution->delivery = $delivery ;
$pointSaleDistribution->save() ;
return ['success'] ;
}
public function actionAjaxProcessActiveDistribution($idDistribution, $active)
{
\Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
$distribution = Distribution::searchOne([
'id' => $idDistribution
]) ;
$distribution->active = (int) $active ;
$distribution->save() ;
return ['success'] ;
}
}

+ 75
- 315
backend/controllers/OrderController.php Ver fichero

@@ -73,98 +73,6 @@ class OrderController extends BackendController
];
}

/**
* Génére un PDF récapitulatif des commandes d'un producteur pour une
* date donnée.
*
* @param string $date
* @param boolean $save
* @param integer $id_etablissement
* @return PDF|null
*/
public function actionReport($date = '', $save = false, $idProducer = 0)
{
if (!Yii::$app->user->isGuest) {
$idProducer = Producer::getId() ;
}
$ordersArray = Order::searchAll([
'distribution.date' => $date,
],
[
'orderby' => 'comment_point_sale ASC, user.name ASC',
'conditions' => 'date_delete IS NULL'
]) ;
$distribution = Distribution::searchOne([],[
'conditions' => 'date LIKE :date',
'params' => [':date' => $date]
]) ;
if ($distribution) {
$selectedProductsArray = ProductDistribution::searchByDistribution($distribution->id) ;
$pointsSaleArray = PointSale::searchAll() ;
foreach ($pointsSaleArray as $pointSale) {
$pointSale->initOrders($ordersArray) ;
}
// produits
$productsArray = Product::searchAll() ;

// get your HTML raw content without any layouts or scripts
$content = $this->renderPartial('report', [
'date' => $date,
'distribution' => $distribution,
'selectedProductsArray' => $selectedProductsArray,
'pointsSaleArray' => $pointsSaleArray,
'productsArray' => $productsArray,
'ordersArray' => $ordersArray
]);

$dateStr = date('d/m/Y', strtotime($date));

if ($save) {
$destination = Pdf::DEST_FILE;
} else {
$destination = Pdf::DEST_BROWSER;
}

$pdf = new Pdf([
// set to use core fonts only
'mode' => Pdf::MODE_UTF8,
// A4 paper format
'format' => Pdf::FORMAT_A4,
// portrait orientation
'orientation' => Pdf::ORIENT_PORTRAIT,
// stream to browser inline
'destination' => $destination,
'filename' => Yii::getAlias('@app/web/pdf/Commandes-' . $date . '-' . $idProducer . '.pdf'),
// your html content input
'content' => $content,
// format content from your own css file if needed or use the
// enhanced bootstrap css built by Krajee for mPDF formatting
//'cssFile' => '@vendor/kartik-v/yii2-mpdf/assets/kv-mpdf-bootstrap.min.css',
// any css to be embedded if required
//'cssInline' => '.kv-heading-1{font-size:18px}',
// set mPDF properties on the fly
//'options' => ['title' => 'Krajee Report Title'],
// call mPDF methods on the fly
'methods' => [
'SetHeader' => ['Commandes du ' . $dateStr],
'SetFooter' => ['{PAGENO}'],
]
]);

// return the pdf output as per the destination setting
return $pdf->render();
}
return null ;
}

/**
* Traite le formulaire d'ajout/modification de commande.
*
@@ -848,86 +756,16 @@ class OrderController extends BackendController
$this->redirect(['index', 'date' => $date]);
}

/**
* Met à jour une commande via une requête AJAX.
*
* @param integer $id_commande
* @param array $produits
* @param string $date
* @param string $commentaire
*/
public function actionAjaxUpdate(
$idOrder, $products, $date, $comment)
{
$order = Order::searchOne(['id' => $idOrder]) ;

if ($order &&
$order->distribution->id_producer == Producer::getId()) {
$products = json_decode($products);
foreach ($products as $key => $quantity) {
$productOrder = ProductOrder::findOne([
'id_order' => $idOrder,
'id_product' => $key
]);

if ($quantity) {
if ($productOrder) {
$productOrder->quantity = $quantity;
} else {
$product = Product::findOne($key);

if ($product) {
$productOrder = new ProductOrder;
$productOrder->id_order = $idOrder;
$productOrder->id_product = $key;
$productOrder->quantity = $quantity;
$productOrder->price = $product->price;
}
}

$productOrder->save();
} else {
if ($productOrder) {
$productOrder->delete();
}
}
}

$order->date_update = date('Y-m-d H:i:s');
$order->comment = $comment;
$order->save();

// data commande
$jsonOrder = $order->getDataJson();

// total point de vente
$pointSale = PointSale::findOne($order->id_point_sale);
$orders = Order::searchAll([
'distribution.date' => $date
], [
'conditions' => 'date_delete IS NULL'
]) ;
$pointSale->initOrders($orders);

echo json_encode([
'total_point_sale' => number_format($pointSale->revenues, 2) . ' €',
'json_order' => $jsonOrder
]);

die();
}
}

/**
* Supprime une commande via une requête AJAX.
*
* @param string $date
* @param integer $idOrder
*/
public function actionAjaxDelete($date, $idOrder)
public function actionAjaxDelete($idOrder)
{
\Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
$order = Order::searchOne([
'id' => $idOrder
]) ;
@@ -950,21 +788,7 @@ class OrderController extends BackendController
ProductOrder::deleteAll(['id_order' => $idOrder]);
}

// total point de vente
$pointSale = PointSale::findOne($order->id_point_sale);
$orders = Order::searchAll([
'distribution.date' => $date,
], [
'conditions' => 'date_delete IS NULL'
]) ;
$pointSale->initOrders($orders);

echo json_encode([
'total_point_sale' => number_format($pointSale->revenues, 2) . ' €',
]);

die();
return ['success'] ;
}
@@ -974,7 +798,7 @@ class OrderController extends BackendController
* @param string $date
* @param integer $idOrder
*/
public function actionDeleteOrder($date, $idOrder)
public function actionDelete($date, $idOrder)
{
$order = Order::searchOne(['id' =>$idOrder]) ;

@@ -1011,6 +835,8 @@ class OrderController extends BackendController
public function actionAjaxCreate(
$date, $idPointSale, $idUser, $username, $products, $comment)
{
\Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
$products = json_decode($products);
$pointSale = PointSale::findOne($idPointSale);
$distribution = Distribution::searchOne([
@@ -1022,6 +848,7 @@ class OrderController extends BackendController
$pointSale &&
count($products) &&
$distribution) {
$order = new Order;
$order->date = date('Y-m-d H:i:s', strtotime($date . ' ' . date('H:i:s')));
$order->id_point_sale = $idPointSale;
@@ -1059,154 +886,85 @@ class OrderController extends BackendController
$productOrder->save();
}
}

// total point de vente
$pointSale = PointSale::findOne($order->id_point_sale);
$orders = Order::searchAll([
'distribution.date' => $date
], [
'conditions' => 'date_delete IS NULL'
]) ;
$pointSale->initOrders($orders);

// json commande
$order = Order::searchOne([
'id' => $order->id
]) ;

$products = [];
foreach ($order->productOrder as $productOrder) {
$products[$productOrder->id_product] = $productOrder->quantity;
}

$jsonOrder = json_encode(['amount' => number_format($order->amount, 2), 'products' => $products]);
$jsonOrder = $order->getDataJson();

$str_user = '';
if ($order->user) {
$strUser = $order->user->name . ' ' . $order->user->lastname;
}
else {
$strUser = $order->username;
}

$strComment = '';
if (strlen($order->comment)) {
$strComment = ' <span class="glyphicon glyphicon-comment"></span>';
}

$strLabelOrderOrigin = '';
if ($order->origin) {
$strLabelOrderOrigin = ' <span class="label label-warning">vous</span>';
}

echo json_encode([
'id_order' => $order->id,
'total_point_sale' => number_format($pointSale->revenues, 2) . ' €',
'order' => '<li>'
. '<a class="btn btn-default" href="javascript:void(0);" '
. 'data-pv-id="' . $idPointSale . '" '
. 'data-id-commande="' . $order->id . '" '
. 'data-commande=\'' . $jsonOrder. '\' '
. 'data-date="' . date('d/m H:i', strtotime($order->date)) . '">'
. '<span class="montant">' . number_format($order->amount, 2) . ' €</span>'
. '<span class="user">' . $strLabelOrderOrigin . ' ' . $strUser . '</span>'
. $strComment
. '</a></li>',
]);
die();
}
return ['success'] ;
}

/**
* Retourne un récapitulatif du total des commandes (potentiel, commandé et
* par produits) au format HTML;
/**
* Met à jour une commande via une requête AJAX.
*
* @param integer $idOrder
* @param array $products
* @param string $date
* @param string $comment
*/
public function actionAjaxTotalOrders($date) {
public function actionAjaxUpdate(
$date, $idOrder, $idPointSale, $idUser, $username, $products, $comment)
{
\Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
$order = Order::searchOne(['id' => $idOrder]) ;

$distribution = Distribution::searchOne([
'date' => $date
]) ;
if ($order &&
$order->distribution->id_producer == Producer::getId()) {
$products = json_decode($products);
foreach ($products as $key => $quantity) {
$productOrder = ProductOrder::findOne([
'id_order' => $idOrder,
'id_product' => $key
]);

if ($distribution) {
// produits
$products = Product::searchAll() ;
if ($quantity) {
if ($productOrder) {
$productOrder->quantity = $quantity;
} else {
$product = Product::findOne($key);

// commandes
$orders = Order::searchAll([
'distribution.date' => $date
]) ;
if ($product) {
$productOrder = new ProductOrder;
$productOrder->id_order = $idOrder;
$productOrder->id_product = $key;
$productOrder->quantity = $quantity;
$productOrder->price = $product->price;
}
}

$revenues = 0;
$weight = 0;
foreach ($orders as $c) {
if(is_null($c->date_delete)) {
$revenues += $c->amount;
$weight += $c->weight;
$productOrder->save();
} else {
if ($productOrder) {
$productOrder->delete();
}
}
}

// produits selec pour production
$selectedProducts = ProductDistribution::searchByDistribution($distribution->id);
$order->id_point_sale = $idPointSale;
$order->date_update = date('Y-m-d H:i:s');
$order->comment = $comment;
if ($idUser) {
$order->id_user = $idUser;

$potentialTurnover = 0;
$totalWeight = 0;
foreach ($selectedProducts as $idSelectedProduct => $selectedProduct) {
if ($selectedProduct['active']) {
foreach ($products as $product) {
if ($product->id == $idSelectedProduct) {
$potentialTurnover += $selectedProduct['quantity_max'] * $product->price;
$totalWeight += $selectedProduct['quantity_max'] * $product->weight / 1000;
}
}
// commentaire du point de vente
$userPointSale = UserPointSale::searchOne([
'id_point_sale' => $order->id_point_sale,
'id_user' => $idUser
]) ;

if ($userPointSale && strlen($userPointSale->comment)) {
$order->comment_point_sale = $userPointSale->comment;
}
} else {
$order->username = $username;
$order->id_user = 0;
}

$htmlTotals = $this->renderPartial('_total_orders.php', [
'arrayProducts' => $products,
'arrayOrders' => $orders,
'arrayProductsSelected' => $selectedProducts,
'revenues' => $revenues,
'totalWeight' => $totalWeight,
'potentialTurnover' => $potentialTurnover,
'weight' => $weight,
]);

echo json_encode([
'html_totals' => $htmlTotals,
]);
$order->save();
}

die();
}

/**
* Active ou désactive la livraison dans un point de vente.
*
* @param integer $id_production
* @param integer $id_point_vente
* @param boolean $bool_livraison
*/
public function actionAjaxPointSaleDelivery(
$idDistribution, $idPointSale, $boolDelivery)
{

$pointSaleDistribution = PointSaleDistribution::searchOne([
'id_distribution' => $idDistribution,
'id_point_sale' => $idPointSale,
]) ;

if ($pointSaleDistribution) {
$pointSaleDistribution->delivery = $boolDelivery;
$pointSaleDistribution->save();
}

die();
}

/**
* Retourne l'état du paiement (historique, crédit) d'une commande donnée.
@@ -1289,13 +1047,15 @@ class OrderController extends BackendController
/**
* Effectue le paiement/remboursement d'une commande.
*
* @param integer $id_commande
* @param integer $idOrder
* @param string $type
* @param float $montant
* @param float $amount
* @return string
*/
public function actionPayment($idOrder, $type, $amount)
public function actionAjaxPayment($idOrder, $type, $amount)
{
\Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
$order = Order::searchOne([
'id' => $idOrder
]) ;
@@ -1310,7 +1070,7 @@ class OrderController extends BackendController
);
}

return $this->actionPaymentStatus($idOrder);
return ['success'];
}

}

+ 482
- 0
backend/views/distribution/index.php Ver fichero

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

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

contact@laboiteapain.net

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

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

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

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

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

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

$this->setTitle('Distributions') ;
$this->setPageTitle('Distributions') ;

?>
<div id="app-distribution-index">
<?php if(strlen($date)): ?>
<span id="distribution-date"><?= $date; ?></span>
<?php endif; ?>
<div id="loading" v-if="showLoading">
<img src="<?= Yii::$app->urlManagerBackend->getBaseUrl(); ?>/img/loader.gif" alt="Chargement ..." />
</div>
<div id="wrapper-app-distribution-index" :class="{'loaded': !loading}">
<div class="col-md-4">
<div id="calendar">
<v-date-picker
is-inline
is-expanded
v-model="date"
popover-visibility="hidden"
:mode="calendar.mode"
:formats="calendar.formats"
:theme-styles="calendar.themeStyles"
:attributes="calendar.attrs"
@dayclick='dayClicked'>
></v-date-picker>
</div>
<div class="clr"></div>
</div>
<div class="col-md-8">
<div v-if="date">
<div id="infos-top">
<div class="col-md-12">
<div class="info-box" v-if="distribution.active">
<span class="info-box-icon bg-green"><i class="fa fa-check"></i></span>
<div class="info-box-content">
<span class="info-box-text">
<h4>Distribution du <strong>{{ dateFormat }}</strong></h4>
<a @click="activeDistribution" data-active="0" class="btn btn-default">Désactiver</a>
</span>
</div>
</div>
<div class="info-box" v-else>
<span class="info-box-icon bg-red"><i class="fa fa-remove"></i></span>
<div class="info-box-content">
<span class="info-box-text">
<h4>Distribution du <strong>{{ dateFormat }}</strong></h4>
<a @click="activeDistribution" data-active="1" class="btn btn-default">Activer</a>
</span>
</div>
</div>
</div>
<!-- produits -->
<div class="col-md-6">
<div class="info-box col-md-4">
<span class="info-box-icon bg-yellow"><i class="fa fa-clone"></i></span>
<div class="info-box-content">
<span class="info-box-text">
{{ countActiveProducts }} Produits<br /><br />
<button class="btn btn-default" @click="showModalProducts = true">Configurer</button>
</span>
</div>
</div>
</div>
<modal v-if="showModalProducts" id="modal-products" @close="showModalProducts = false">
<h3 slot="header">Produits</h3>
<div slot="body">
<table class="table table-condensed table-bordered table-hover">
<thead>
<tr>
<td>Actif</td>
<td>Nom</td>
<td class="quantity-ordered">Commandé</td>
<td class="quantity-max">Maximum</td>
</tr>
</thead>
<tbody>
<tr v-for="product in products">
<td>
<button class="btn btn-success" v-if="product.productDistribution[0].active == 1"><span class="glyphicon glyphicon-ok"></span></button>
<button class="btn btn-default" v-else data-active-product="1" :data-id-product="product.id" @click="productActiveClick"><span class="glyphicon glyphicon-ok"></span></button>
<button class="btn btn-danger" v-if="product.productDistribution[0].active == 0"><span class="glyphicon glyphicon-remove"></span></button>
<button class="btn btn-default" v-else data-active-product="0" :data-id-product="product.id" @click="productActiveClick"><span class="glyphicon glyphicon-remove"></span></button>
</td>
<td>{{ product.name }}</td>
<td class="quantity-ordered">{{ product.quantity_ordered ? product.quantity_ordered : '-' }}</td>
<td class="quantity-max"><input type="text" class="form-control quantity-max" placeholder="&infin;" :data-id-product="product.id" :value="product.productDistribution[0].quantity_max" @keyup="productQuantityMaxChange" /></td>
</tr>
</tbody>
</table>
</div>
</modal>
<div class="col-md-6">
<div class="info-box col-md-4">
<span class="info-box-icon bg-yellow"><i class="fa fa-map-marker"></i></span>
<div class="info-box-content">
<span class="info-box-text">
{{ countActivePointsSale }} Points de vente<br /><br />
<button class="btn btn-default" @click="showModalPointsSale = true">Configurer</button>
</span>
</div>
</div>
</div>
<modal v-if="showModalPointsSale" id="modal-points-sale" @close="showModalPointsSale = false">
<h3 slot="header">Points de vente</h3>
<div slot="body">
<table class="table table-condensed table-bordered table-hover">
<thead>
<tr>
<td>Actif</td>
<td>Nom</td>
</tr>
</thead>
<tbody>
<tr v-for="pointSale in pointsSale">
<td>
<button class="btn btn-success" v-if="pointSale.pointSaleDistribution[0].delivery == 1"><span class="glyphicon glyphicon-ok"></span></button>
<button class="btn btn-default" v-else data-delivery-point-sale="1" :data-id-point-sale="pointSale.id" @click="pointSaleActiveClick"><span class="glyphicon glyphicon-ok"></span></button>
<button class="btn btn-danger" v-if="pointSale.pointSaleDistribution[0].delivery == 0"><span class="glyphicon glyphicon-remove"></span></button>
<button class="btn btn-default" v-else data-delivery-point-sale="0" :data-id-point-sale="pointSale.id" @click="pointSaleActiveClick"><span class="glyphicon glyphicon-remove"></span></button>
</td>
<td>{{ pointSale.name }}</td>
</tr>
</tbody>
</table>
</div>
</modal>
<div class="col-md-6">
<div class="info-box col-md-4">
<span class="info-box-icon bg-yellow"><i class="fa fa-euro"></i></span>
<div class="info-box-content">
<span class="info-box-text">Commandé</span>
<span class="info-box-number">{{ distribution.revenues }} € / {{ distribution.weight }} kg</span>
<span class="info-box-text">Potentiel</span>
<span class="info-box-number">{{ distribution.potential_revenues }} € / {{ distribution.potential_weight }} kg</span>
</div>
</div>
</div>
<div class="col-md-6">
<div class="info-box col-md-4">
<span class="info-box-icon bg-yellow"><i class="fa fa-download"></i></span>
<div class="info-box-content">
<span class="info-box-text">
{{ orders.length }} Commande<span v-if="orders.length">s</span><br /><br />
<a :href="distribution.url_report" class="btn btn-default" v-if="orders.length">Télécharger (PDF)</a>
</span>
</div>
</div>
</div>
</div>
</div>

<div class="callout callout-info" v-else>
<h4><i class="fa fa-info"></i> Pour commencer</h4>
<p>Veuillez choisir une date de distribution.</p>
</div>
</div>

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

<div id="orders" class="panel panel-default" v-if="date">
<div class="panel-heading">
<h3 class="panel-title">Commandes <label class="label label-success" v-if="orders.length">{{ orders.length }}</label><label class="label label-danger" v-else>0</label></h3>
</div>
<div class="panel-body">
<button id="btn-add-order" @click="showModalFormOrderCreate = true" class="btn btn-default"><span class="glyphicon glyphicon-plus"></span> Ajouter une commande</button>
<order-form
v-if="showModalFormOrderCreate"
:date="date"
:order="orderCreate"
:points-sale="pointsSale"
:users="users"
:products="products"
@close="showModalFormOrderCreate = false"
@ordercreatedupdated="orderCreatedUpdated"
></order-form>
<div id="wrapper-nav-points-sale">
<ul id="nav-points-sale">
<li data-id-point-sale="0" data-id-point-sale="0" v-if="countActivePointsSale > 1" @click="pointSaleClick">
<a class="btn btn-default btn-primary" v-if="idActivePointSale == 0">Tous <span class="label label-default">{{ orders.length }}</span> <span class="glyphicon glyphicon-triangle-bottom"></span></a>
<a class="btn btn-default" v-else>Tous <span class="label label-default">{{ orders.length }}</span><span class="glyphicon glyphicon-triangle-bottom"></span></a>
</li>
<li v-for="pointSale in pointsSale" :data-id-point-sale="pointSale.id" v-if="pointSale.pointSaleDistribution[0].delivery == 1" @click="pointSaleClick">
<a class="btn btn-default btn-primary" v-if="idActivePointSale == pointSale.id">{{ pointSale.name }} <span class="label label-default">{{ countOrdersByPointSale[pointSale.id] }}</span><span class="glyphicon glyphicon-triangle-bottom"></span></a>
<a class="btn btn-default" v-else>{{ pointSale.name }} <span class="label label-default">{{ countOrdersByPointSale[pointSale.id] }}</span><span class="glyphicon glyphicon-triangle-bottom"></span></a>
</li>
</ul>
<div class="clr"></div>
</div>
<table class="table table-condensed table-bordered table-hover" v-if="countOrdersByPointSale[idActivePointSale] > 0 || (idActivePointSale == 0 && orders.length > 0)">
<tbody>
<template v-for="order in orders" v-if="idActivePointSale == 0 || idActivePointSale == order.id_point_sale">
<tr>
<td>
<label class="label label-success" v-if="order.origin == 'user'">client</label>
<label class="label label-default" v-else-if="order.origin == 'auto'">auto</label>
<label class="label label-warning" v-else>admin</label>
</td>
<td>
<span v-if="order.user">{{ order.user.name +' '+order.user.lastname }}</span>
<span v-else>{{ order.username }}</span>
</td>
<td v-if="idActivePointSale == 0">
{{ order.pointSale.name }}
</td>
<td>{{ order.amount.toFixed(2).replace(/(\d)(?=(\d{3})+(?:\.\d+)?$)/g, "$1,")+' €' }}</td>
<td>
<span class="label label-success" v-if="order.amount_paid == order.amount">payé</span>
<span class="label label-default" v-else-if="order.amount_paid == 0">non réglé</span>
<span class="label label-default" v-else-if="order.amount_paid > order.amount">surplus</span>
<span class="label label-warning" v-else-if="order.amount_paid < order.amount">reste à payer</span>
</td>
<td class="column-actions">
<button class="btn btn-default" :data-id-order="order.id" @click="orderViewClick"><span :class="'glyphicon ' + ((showViewProduct && idOrderView == order.id) ? 'glyphicon-eye-close' : 'glyphicon-eye-open')"></span></button>
<button class="btn btn-default" :data-id-order="order.id" @click="orderPaymentModalClick"><span class="glyphicon glyphicon-euro"></span></button>
<button class="btn btn-default" :data-id-order="order.id" @click="updateOrderClick"><span class="glyphicon glyphicon-pencil"></span></button>
<button class="btn btn-default" :data-id-order="order.id" @click="deleteOrderClick"><span class="glyphicon glyphicon-trash"></span></button>

<order-form
v-if="showModalFormOrderUpdate && idOrderUpdate == order.id"
:date="date"
:id-point-sale="idActivePointSale"
:points-sale="pointsSale"
:users="users"
:products="products"
:order="order"
@close="showModalFormOrderUpdate = false"
@ordercreatedupdated="orderCreatedUpdated"
></order-form>
<modal v-if="showModalPayment && idOrderPayment == order.id" class="modal-payment" @close="showModalPayment = false">
<h3 slot="header">
Commande du <strong>{{ dateFormat }}</strong> &gt;
<strong><span v-if="order.user">{{ order.user.name +' '+order.user.lastname }}</span>
<span v-else>{{ order.username }}</span></strong>
</h3>
<div slot="body">
<div class="col-md-4">
<div class="info-box">
<span :class="'info-box-icon ' +((order.amount_paid == order.amount) ? 'bg-green' : 'bg-red')"><i class="fa fa-check"></i></span>
<div class="info-box-content">
<span class="info-box-text">Montant</span>
<span class="info-box-number">{{ order.amount.toFixed(2).replace(/(\d)(?=(\d{3})+(?:\.\d+)?$)/g, "$1,")+' €' }}</span>
<span class="info-box-text">
Statut<br />
<span class="label label-success" v-if="order.amount_paid == order.amount">payé</span>
<span class="label label-default" v-else-if="order.amount_paid == 0">non réglé</span>
<span class="label label-default" v-else-if="order.amount_paid > order.amount">surplus</span>
<span class="label label-warning" v-else-if="order.amount_paid < order.amount">reste à payer</span>
</span>
</div>
</div>
<div class="info-box">
<span class="info-box-icon bg-yellow"><i class="fa fa-user"></i></span>
<div class="info-box-content">
<span class="info-box-text">Crédit utilisateur</span>
<span class="info-box-number">24,00 €</span>
</div>
</div>
<button v-if="order.amount_paid == order.amount"
class="btn btn-default"
:data-amount="order.amount"
data-type="refund"
@click="orderPaymentClick" >
<span class="glyphicon glyphicon-chevron-right"></span>
Rembourser {{ order.amount.toFixed(2).replace(/(\d)(?=(\d{3})+(?:\.\d+)?$)/g, "$1,")+' €' }}
</button>
<button v-else-if="order.amount_paid == 0"
class="btn btn-default"
:data-amount="order.amount"
data-type="payment"
@click="orderPaymentClick">
<span class="glyphicon glyphicon-chevron-right"></span>
Payer {{ order.amount.toFixed(2).replace(/(\d)(?=(\d{3})+(?:\.\d+)?$)/g, "$1,")+' €' }}
</button>
<button v-else-if="order.amount_paid > order.amount"
class="btn btn-default"
:data-amount="order.amount_surplus"
data-type="refund"
@click="orderPaymentClick">
<span class="glyphicon glyphicon-chevron-right"></span>
Rembourser {{ order.amount_surplus.toFixed(2).replace(/(\d)(?=(\d{3})+(?:\.\d+)?$)/g, "$1,")+' €' }}
</button>
<button v-else-if="order.amount_paid < order.amount"
class="btn btn-default"
:data-amount="order.amount_remaining"
data-type="payment"
@click="orderPaymentClick">
<span class="glyphicon glyphicon-chevron-right"></span>
Payer le restant {{ order.amount_remaining.toFixed(2).replace(/(\d)(?=(\d{3})+(?:\.\d+)?$)/g, "$1,")+' €' }}
</button>
</div>
<div class="col-md-8">
<h4>Historique paiement</h4>
<table class="table table-condensed table-bordered table-hover">
<thead>
<tr>
<td>Date</td>
<td>Utilisateur</td>
<td>Action</td>
<td>- Débit</td>
<td>+ Crédit</td>
</tr>
</thead>
<tbody>
<tr v-for="creditHistory in order.creditHistory">
<td>{{ creditHistory.date }}</td>
<td>{{ creditHistory.user_action }}</td>
<td v-html="creditHistory.wording"></td>
<td>{{ creditHistory.debit }}</td>
<td>{{ creditHistory.credit }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</modal>
</td>
</tr>
<tr class="view" v-if="showViewProduct && idOrderView == order.id">
<td colspan="6">
<strong><span class="glyphicon glyphicon-menu-right"></span> Produits</strong>
<ul>
<li v-for="product in products" v-if="order.productOrder[product.id] > 0">
{{ order.productOrder[product.id] }} x {{ product.name }}
</li>
</ul>
</td>
</tr>
</template>
</tbody>
</table>
<div class="alert alert-warning" v-else>
Aucune commande
</div>
</div>
</div>
</div>
</div>

<!-- template for the order-form component -->
<script type="text/x-template" id="order-form-template">
<modal class="modal-form-order" @close="$emit('close')">
<h3 slot="header">Ajouter une commande</h3>
<div slot="body">
<div class="callout callout-warning" v-if="errors.length">
<ul>
<li v-for="error in errors">{{ error }}</li>
</ul>
</div>
<div class="col-md-4">
<div class="form-group">
<label class="control-label" for="select-id-point-sale">Point de vente</label>
<select class="form-control" id="select-id-point-sale" v-model="order.id_point_sale">
<option v-for="pointSale in pointsSale" v-if="pointSale.pointSaleDistribution[0].delivery == 1" :value="pointSale.id">{{ pointSale.name }}</option>
</select>
</div>
<div class="form-group">
<label class="control-label" for="select-id-user">Utilisateur</label>
<select class="form-control" v-model="order.id_user">
<option v-for="user in users" :value="user.id_user">{{ user.name +' '+ user.lastname }}</option>
</select>
<input v-model="order.username" type="text" class="form-control" placeholder="Ou saisissez ici le nom de l'utilisateur" />
</div>
<div class="form-group">
<label class="control-label" for="textarea-comment">Commentaire</label>
<textarea class="form-control" id="textarea-comment" v-model="order.comment"></textarea>
</div>
</div>
<div class="col-md-8">
<label class="control-label">Produits</label>
<table class="table table-condensed table-bordered table-hover table-products">
<tbody>
<tr v-for="product in products" :class="(order.productOrder[product.id] > 0) ? 'product-ordered' : ''">
<td>
<span class="label label-success" v-if="product.productDistribution[0].active == 1">Actif</span>
<span class="label label-danger" v-else>Inactif</span>
</td>
<td>{{ product.name }}</td>
<td class="quantity">
<div class="input-group">
<span class="input-group-btn">
<button class="btn btn-default btn-moins" type="button" @click="productQuantityClick(product.id, -1)"><span class="glyphicon glyphicon-minus"></span></button>
</span>
<input type="text" v-model="order.productOrder[product.id]" class="form-control" />
<span class="input-group-btn">
<button class="btn btn-default btn-plus" type="button" @click="productQuantityClick(product.id, 1)"><span class="glyphicon glyphicon-plus"></span></button>
</span>
</div>
</td>
<td class="quantity-remaining">/ {{ product.quantity_remaining }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div slot="footer">
<button class="modal-default-button btn btn-primary" @click="submitFormUpdate" v-if="order.id">Modifier</button>
<button class="modal-default-button btn btn-primary" @click="submitFormCreate" v-else>Créer</button>
<button class="modal-default-button btn btn-default" @click="$emit('close')">Annuler</button>
</div>
</modal>
</script>

<!-- template for the modal component -->
<script type="text/x-template" id="modal-template">
<transition name="modal">
<div class="modal-mask">
<div class="modal-wrapper">
<div class="modal-container">

<div class="modal-header">
<slot name="header"></slot>
</div>

<div class="modal-body">
<slot name="body"></slot>
</div>

<div class="modal-footer">
<slot name="footer">
<button class="modal-default-button btn btn-default" @click="$emit('close')">Fermer</button>
</slot>
</div>
</div>
</div>
</div>
</transition>
</script>

+ 207
- 0
backend/views/distribution/report.php Ver fichero

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

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

contact@laboiteapain.net

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

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

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

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

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

use common\models\Order ;

$dayWeek = date('w', strtotime($date));
$dayWeekArray = [0 => 'sunday', 1 => 'monday', 2 => 'tuesday', 3 => 'wednesday', 4 => 'thursday', 5 => 'friday', 6 => 'saturday'];
$fieldInfosPointSale = 'infos_' . $dayWeekArray[$dayWeek];

$html = '' ;

// par point de vente
foreach ($pointsSaleArray as $pointSale) {
if (count($pointSale->orders) && strlen($pointSale->$fieldInfosPointSale)) {
$html .= '<h3>'.$pointSale->name.'</h3>' ;
$colCredit = ($pointSale->credit) ? '<th>Rappel crédit</th>' : '' ;
$html .= '<table class="table table-bordered">'
. '<thead>'
. '<tr>'
. '<th>Client</th>'
. '<th>Produits</th>'
. '<th>Commentaire</th>'
. $colCredit
. '<th>Montant</th>'
. '</tr>'
. '<tbody>';
foreach ($pointSale->orders as $order) {
$html .= '<tr>' ;
$strUser = '';

// username
if ($order->user) {
$strUser = $order->user->name . " " . $order->user->lastname;
} else {
$strUser = $order->username;
}
if(strlen($order->comment_point_sale))
{
$strUser .= '<br /><em>'.$order->comment_point_sale.'</em>' ;
}
// téléphone
if (isset($order->user) && strlen($order->user->phone)) {
$strUser .= '<br />' . $order->user->phone . '';
}

$html .= '<td>'.$strUser.'</td>';
// produits
$strProducts = '';
foreach ($productsArray as $product) {
$add = false;
foreach ($order->productOrder as $productOrder) {
if ($product->id == $productOrder->id_product) {
$strProducts .= $productOrder->quantity . '&nbsp;' . $product->name . ', ';
$add = true;
}
}
}
$html .= '<td>'.substr($strProducts, 0, strlen($strProducts) - 2).'</td>';
$html .= '<td>'.$order->comment.'</td>';
if($pointSale->credit) {
$credit = '' ;
if(isset($order->user) && isset($order->user->userProducer)) {
$credit = number_format($order->user->userProducer[0]->credit,2).' €' ;
}
$html .= '<td>'.$credit.'</td>' ;
}
$html .= '<td><strong>'.number_format($order->amount, 2) . ' € ';

if($order->getPaymentStatus() == Order::PAYMENT_PAID)
{
$html .= '(payé)' ;
}
elseif($order->getPaymentStatus() == Order::PAYMENT_UNPAID && $order->getAmount(Order::AMOUNT_PAID))
{
$html .= '(reste '.$order->getAmount(Order::AMOUNT_REMAINING, true).' à payer)' ;
}
elseif($order->getPaymentStatus() == Order::PAYMENT_SURPLUS)
{
$html .= '(surplus : '.$order->getAmount(Order::PAYMENT_SURPLUS, true).' à rembourser)' ;
}
$html .= '</strong></td>' ;
$html .= '</tr>' ;
}

$html .= '<tr><td><strong>Total</strong></td>' ;
$strProducts = '';
foreach ($productsArray as $product) {
$quantity = Order::getProductQuantity($product->id, $pointSale->orders);
$strQuantity = '';
if ($quantity) {
$strQuantity = $quantity;
$strProducts .= $strQuantity .'&nbsp;'. $product->name . ', ';
}
}
$strProducts = substr($strProducts, 0, strlen($strProducts) - 2) ;
$html .= '<td>'.$strProducts.'</td><td></td>' ;
if($pointSale->credit) {
$html .= '<td></td>' ;
}
$html .= '<td><strong>'.number_format($pointSale->revenues, 2) . ' €</strong></td>';
$html .= '</tbody></table><pagebreak>' ;
}
}

// par point de vente
$html .= '<h3>Points de vente</h3>' ;
$html .= '<table class="table table-bordered">'
. '<thead>'
. '<tr>'
. '<th>Point de vente</th>'
. '<th>Produits</th>'
. '<th>Montant</th>'
. '</tr>'
. '<tbody>';

$revenues = 0 ;
foreach ($pointsSaleArray as $pointSale)
{
if (count($pointSale->orders) && strlen($pointSale->$fieldInfosPointSale))
{
$html .= '<tr><td>'.$pointSale->name.'</td><td>' ;
foreach ($productsArray as $product) {
$quantity = Order::getProductQuantity($product->id, $pointSale->orders);
$strQuantity = ($quantity) ? $quantity : '' ;
if(strlen($strQuantity)) {
$html .= $strQuantity . '&nbsp;'.$product->name.', ' ;
}
}
$html = substr($html, 0, strlen($html) - 2) ;
$html .= '</td><td>'.number_format($pointSale->revenues, 2).' €</td></tr>' ;
$revenues += $pointSale->revenues ;
}
}

// total
$html .= '<tr><td><strong>Total</strong></td><td>' ;
foreach ($productsArray as $product) {
$quantity = Order::getProductQuantity($product->id, $ordersArray);
if($quantity) {
$html .= $quantity . '&nbsp;'.$product->name.', ' ;
}
}

$html = substr($html, 0, strlen($html) - 2) ;

$html .= '</td><td><strong>'.number_format($revenues, 2).' €</strong></td></tr>' ;

$html .= '</tbody></table>' ;

echo $html ;

?>

+ 2
- 2
backend/views/layouts/left.php Ver fichero

@@ -47,11 +47,11 @@ termes.
'options' => ['class' => 'sidebar-menu tree', 'data-widget'=> 'tree'],
'items' => [
['label' => 'Tableau de bord','icon' => 'dashboard','url' => ['/site/index'], 'visible' => User::isCurrentProducer()],
['label' => 'Distributions','icon' => 'calendar','url' => ['/order/index'], 'visible' => User::isCurrentProducer()],
['label' => 'Abonnements','icon' => 'repeat','url' => ['/subscription/index'], 'visible' => User::isCurrentProducer(), 'active' => Yii::$app->controller->id == 'subscription'],
['label' => 'Distributions','icon' => 'calendar','url' => ['/distribution/index'], 'visible' => User::isCurrentProducer()],
['label' => 'Produits','icon' => 'clone','url' => ['/product/index'], 'visible' => User::isCurrentProducer(), 'active' => Yii::$app->controller->id == 'product'],
['label' => 'Points de vente','icon' => 'map-marker','url' => ['/point-sale/index'], 'visible' => User::isCurrentProducer(), 'active' => Yii::$app->controller->id == 'point-sale'],
['label' => 'Utilisateurs','icon' => 'users','url' => ['/user/index'], 'visible' => User::isCurrentProducer(), 'active' => Yii::$app->controller->id == 'user'],
['label' => 'Abonnements','icon' => 'repeat','url' => ['/subscription/index'], 'visible' => User::isCurrentProducer(), 'active' => Yii::$app->controller->id == 'subscription'],
['label' => 'Paramètres','icon' => 'cog','url' => ['/producer/update'], 'visible' => User::isCurrentProducer()],
['label' => 'Communiquer','icon' => 'bullhorn','url' => ['/communicate/index'], 'visible' => User::isCurrentProducer()],
[

+ 2
- 1
backend/views/layouts/main.php Ver fichero

@@ -54,6 +54,7 @@ if (Yii::$app->controller->action->id === 'login') {
} else {

\dmstr\web\AdminLteAsset::register($this);
\common\assets\CommonAsset::register($this);
\backend\assets\AppAsset::register($this);

$directoryAsset = Yii::$app->assetManager->getPublishedUrl('@vendor/almasaeed2010/adminlte/dist');
@@ -71,7 +72,7 @@ if (Yii::$app->controller->action->id === 'login') {
<link rel="icon" type="image/png" href="<?php echo Yii::$app->urlManagerBackend->getBaseUrl(); ?>/img/favicon3.png" />
<?php $this->head() ?>
</head>
<body class="hold-transition <?= \dmstr\helpers\AdminLteHelper::skinClass() ?> sidebar-mini">
<body class="<?= Yii::$app->controller->id.'-'.Yii::$app->controller->action->id ?> hold-transition <?= \dmstr\helpers\AdminLteHelper::skinClass() ?> sidebar-mini">
<?php $this->beginBody() ?>
<div class="wrapper">


+ 1
- 1
backend/views/site/index.php Ver fichero

@@ -112,7 +112,7 @@ $this->title = 'Tableau de bord';
</span>
<span class="info-box-number"></span>
<div class="buttons">
<?= Html::a('<span class="fa fa-eye"></span>', ['order/index', 'date' => $distribution->date], ['class' => 'btn btn-default']); ?>
<?= Html::a('<span class="fa fa-eye"></span>', ['distribution/index', 'date' => $distribution->date], ['class' => 'btn btn-default']); ?>
<?php if(count($distribution->order)): ?><?= Html::a('<span class="fa fa-download"></span>', ['order/report', 'date' => $distribution->date], ['class' => 'btn btn-default']); ?><?php endif; ?>
</div>
</div>

BIN
backend/web/.sass-cache/2a0ffb00578c9d5a537db16d14c734a22b18f35c/screen.scssc Ver fichero


+ 549
- 236
backend/web/css/screen.css
La diferencia del archivo ha sido suprimido porque es demasiado grande
Ver fichero


BIN
backend/web/img/loader.gif Ver fichero

Antes Después
Anchura: 64  |  Altura: 64  |  Tamaño: 10KB

+ 2
- 2
backend/web/js/lechatdesnoisettes.js Ver fichero

@@ -35,7 +35,7 @@ termes.
*/

$(document).ready(function() {
chat_calendar() ;
//chat_calendar() ;
chat_datepicker() ;
chat_vrac() ;
chat_email_masse() ;
@@ -704,7 +704,7 @@ function chat_vrac() {

function chat_datepicker() {
$('.datepicker').datepicker({dateFormat:'dd/mm/yy'}) ;
$('input.datepicker').datepicker({dateFormat:'dd/mm/yy'}) ;
}


+ 402
- 0
backend/web/js/vuejs/distribution-index.js Ver fichero

@@ -0,0 +1,402 @@


var app = new Vue({
el: '#app-distribution-index',
data: {
date: null,
dateFormat: null,
loading: true,
distribution: {
active: false,
},
products: [],
countActiveProducts: 0,
pointsSale: [],
idActivePointSale: 0,
countActivePointsSale: 0,
countOrdersByPointSale: [],
orders: [],
users: [],
showModalProducts: false,
showModalPointsSale: false,
showModalFormOrderCreate: false,
orderCreate: null,
showModalFormOrderUpdate: false,
idOrderUpdate: 0,
showViewProduct: false,
idOrderView: 0,
showModalPayment: false,
idOrderPayment: 0,
showLoading: false,
calendar: {
mode: 'single',
attrs: [],
themeStyles: {
wrapper: {
background: '#BB8757',
color: '#fafafa',
},
header: {
padding: '10px 10px',
},
headerHorizontalDivider: {
borderTop: 'solid rgba(255, 255, 255, 0.2) 1px',
width: '80%',
},
weekdays: {
color: 'white',
fontWeight: '600',
padding: '10px 10px',
fontSize: '2rem'
},
weeks: {
padding: '0 15px 15px 15px',
},
dayContent: function(object) {
var style = {
fontSize: '2rem',
padding: '16px',
};
if(object.isHovered || object.isFocus) {
style.backgroundColor = '#F39C12' ;
}
return style ;
},
},
formats: {
dayPopover: 'DD/MM/YYYY'
}
},
},
mounted: function() {
if($('#distribution-date').size()) {
this.date = new Date($('#distribution-date').html()) ;
this.dateFormat = ('0' + this.date.getDate()).slice(-2)+ '/'
+ ('0' + (this.date.getMonth() +1)).slice(-2) + '/'
+ this.date.getFullYear() ;
}
this.init() ;
this.loading = false ;
},
methods: {
getDate: function() {
return this.formatDate(this.date) ;
},
formatDate: function(date) {
if(date) {
return date.getFullYear() + '-'
+ ('0' + (date.getMonth() +1)).slice(-2) + '-'
+ ('0' + date.getDate()).slice(-2) ;
}
return false ;
},
init: function(idActivePointSale) {
this.showLoading = true ;
axios.get("ajax-infos",{params: {date : this.getDate()}})
.then(response => {
this.distribution = response.data.distribution ;
this.products = response.data.products ;
this.initCountActiveProducts() ;
if(response.data.orders) {
this.orders = response.data.orders ;
}
else {
this.orders = [] ;
}
if(response.data.order_create) {
this.orderCreate = response.data.order_create ;
}
if(response.data.points_sale) {
this.pointsSale = response.data.points_sale ;
this.initPointsSale(idActivePointSale) ;
}
else {
this.pointsSale = [] ;
}
if(response.data.users) {
this.users = response.data.users ;
}
this.calendar.attrs = [] ;
var distributions = response.data.distributions ;
if(distributions.length) {
for(var i= 0; i < distributions.length; i++) {
this.calendar.attrs.push({
highlight: {
backgroundColor: '#00A65A',
},
dates: distributions[i].date,
}) ;
}
}
this.showLoading = false ;
}) ;
},
initCountActiveProducts: function() {
this.countActiveProducts = 0 ;
for(var i= 0; i < this.products.length; i++) {
if(this.products[i].productDistribution[0].active == 1) {
this.countActiveProducts ++ ;
}
}
},
initPointsSale: function(idActivePointSale) {
this.countActivePointsSale = 0 ;
this.setIdActivePointSale(0) ;
for(var i= 0; i < this.pointsSale.length; i++) {
if(this.pointsSale[i].pointSaleDistribution[0].delivery == 1) {
this.countActivePointsSale ++ ;
this.setIdActivePointSale(this.pointsSale[i].id) ;
}
}
if(this.countActivePointsSale > 1) {
this.setIdActivePointSale(0) ;
}
if(idActivePointSale) {
this.setIdActivePointSale(idActivePointSale) ;
}
this.countOrdersByPointSale = [] ;
for(var i = 0; i < this.pointsSale.length ; i++) {
this.countOrdersByPointSale[this.pointsSale[i].id] = 0 ;
}
for(var i = 0; i < this.orders.length ; i++) {
this.countOrdersByPointSale[this.orders[i].id_point_sale] ++ ;
}
},
dayClicked: function(day) {
this.date = day.date ;
this.dateFormat = ('0' + this.date.getDate()).slice(-2)+ '/'
+ ('0' + (this.date.getMonth() +1)).slice(-2) + '/'
+ this.date.getFullYear() ;
this.init() ;
},
productQuantityMaxChange: function(event) {
axios.get("ajax-process-product-quantity-max",{params: {
idDistribution: this.distribution.id,
idProduct: event.currentTarget.getAttribute('data-id-product'),
quantityMax: event.currentTarget.value
}})
.then(response => {

}) ;
},
productActiveClick: function(event) {
var idProduct = event.currentTarget.getAttribute('data-id-product') ;
var activeProduct = event.currentTarget.getAttribute('data-active-product') ;
axios.get("ajax-process-active-product",{params: {
idDistribution: this.distribution.id,
idProduct: idProduct,
active: activeProduct
}})
.then(response => {
}) ;
for(i = 0 ; i < this.products.length ; i++) {
if(this.products[i].id == idProduct) {
this.products[i].productDistribution[0].active = activeProduct ;
}
}
this.initCountActiveProducts() ;
},
pointSaleActiveClick: function(event) {
var idPointSale = event.currentTarget.getAttribute('data-id-point-sale') ;
var deliveryPointSale = event.currentTarget.getAttribute('data-delivery-point-sale') ;
axios.get("ajax-process-active-point-sale",{params: {
idDistribution: this.distribution.id,
idPointSale: idPointSale,
delivery: deliveryPointSale
}})
.then(response => {
}) ;
for(i = 0 ; i < this.pointsSale.length ; i++) {
if(this.pointsSale[i].id == idPointSale) {
this.pointsSale[i].pointSaleDistribution[0].delivery = deliveryPointSale ;
}
}
this.initPointsSale() ;
},
activeDistribution: function(event) {
axios.get("ajax-process-active-distribution",{params: {
idDistribution: this.distribution.id,
active: event.currentTarget.getAttribute('data-active')
}})
.then(response => {
this.init() ;
}) ;
},
pointSaleClick: function(event) {
this.setIdActivePointSale(event.currentTarget.getAttribute('data-id-point-sale')) ;
},
setIdActivePointSale: function(id) {
this.idActivePointSale = id ;
this.orderCreate.id_point_sale = id ;
},
orderCreatedUpdated: function() {
this.showModalFormOrderCreate = false ;
this.showModalFormOrderUpdate = false ;
this.init(this.idActivePointSale) ;
},
deleteOrderClick: function(event) {
var idOrder = event.currentTarget.getAttribute('data-id-order') ;
axios.get(UrlManager.getBaseUrlAbsolute()+"order/ajax-delete",{params: {
idOrder: idOrder
}})
.then(response => {
this.init(this.idActivePointSale) ;
}) ;
},
updateOrderClick: function(event) {
var idOrder = event.currentTarget.getAttribute('data-id-order') ;
this.idOrderUpdate = idOrder ;
this.showModalFormOrderUpdate = true ;
},
orderPaymentModalClick: function(event) {
var idOrder = event.currentTarget.getAttribute('data-id-order') ;
this.idOrderPayment = idOrder ;
this.showModalPayment = true ;
},
orderPaymentClick: function(event) {
var idOrder = event.currentTarget.getAttribute('data-id-order') ;
axios.get(UrlManager.getBaseUrlAbsolute()+"order/ajax-payment",{params: {
idOrder: this.idOrderPayment,
type: event.currentTarget.getAttribute('data-type'),
amount: event.currentTarget.getAttribute('data-amount')
}})
.then(response => {
this.init(this.idActivePointSale) ;
}) ;
},
orderViewClick: function(event) {
var currentIdOrderView = event.currentTarget.getAttribute('data-id-order') ;
if(this.idOrderView == currentIdOrderView) {
this.showViewProduct = !this.showViewProduct ;
}
else {
this.showViewProduct = true ;
this.idOrderView = currentIdOrderView ;
}
}
},
});

Vue.component('modal', {
template: '#modal-template'
})

Vue.component('order-form',{
props: ['date', 'pointsSale', 'users', 'products', 'order'],
data: function() {
return {
errors: [],
idPointSale: 0,
idUser: 0,
username : '',
comment: '',
} ;
},
template: '#order-form-template',
methods: {
checkForm: function() {
this.errors = [] ;
var countProducts = 0 ;
for(var key in this.order.productOrder) {
if(this.order.productOrder[key] > 0) {
countProducts ++ ;
}
}
if(this.order.id_point_sale
&& (this.order.id_user || (this.order.username && this.order.username.length))
&& countProducts > 0) {
return true ;
}
if(!this.order.id_point_sale) {
this.errors.push('Veuillez sélectionner un point de vente') ;
}
if(!this.order.id_user && !this.order.username) {
this.errors.push('Veuillez sélectionner ou saisir un utilisateur') ;
}
if(!countProducts) {
this.errors.push('Veuillez sélectionner au moins un produit') ;
}
},
submitFormCreate: function() {
if(this.checkForm()) {
axios.get(UrlManager.getBaseUrlAbsolute()+"order/ajax-create",{params: {
date: this.date.getFullYear() + '-'
+ ('0' + (this.date.getMonth() +1)).slice(-2) + '-'
+ ('0' + this.date.getDate()).slice(-2),
idPointSale: this.order.id_point_sale,
idUser: this.order.id_user,
username: this.order.username,
products: JSON.stringify(this.order.productOrder),
comment: this.order.comment
}})
.then(response => {
this.order.id_point_sale = 0 ;
this.order.id_user = 0 ;
this.order.username = '' ;
this.order.comment = '' ;
for(i=0 ; i<this.order.productOrder.length ; i++) {
this.order.productOrder[i] = 0 ;
}

this.$emit('ordercreatedupdated') ;
}) ;
}
},
submitFormUpdate: function() {
if(this.checkForm()) {
axios.get(UrlManager.getBaseUrlAbsolute()+"order/ajax-update",{params: {
date: this.date.getFullYear() + '-'
+ ('0' + (this.date.getMonth() +1)).slice(-2) + '-'
+ ('0' + this.date.getDate()).slice(-2),
idOrder: this.order.id,
idPointSale: this.order.id_point_sale,
idUser: this.order.id_user,
username: ''+this.order.username,
products: JSON.stringify(this.order.productOrder),
comment: this.comment
}})
.then(response => {
this.$emit('ordercreatedupdated') ;
}) ;
}
},
productQuantityClick: function(id_product, quantity) {
if(this.order.productOrder[id_product] + quantity >= 0) {
this.order.productOrder[id_product] += quantity ;
}
}
}
}) ;

+ 4
- 1
backend/web/sass/_adminlte.scss Ver fichero

@@ -108,7 +108,7 @@ body.skin-black {
color: #333 ;
}
.btn-primary, .btn-success {
.btn-primary {
background-color: $color1 ;
color: white ;
border-color: $color1 ;
@@ -121,6 +121,9 @@ body.skin-black {
}
.callout {
h4 .fa {
margin-right: 7px ;
}
a {
color: white ;
}

+ 351
- 0
backend/web/sass/distribution/_index.scss Ver fichero

@@ -0,0 +1,351 @@
/**
Copyright La boîte à pain (2018)

contact@laboiteapain.net

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

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

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

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

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

.distribution-index {
#wrapper-app-distribution-index {
display: none ;
&.loaded {
display: block ;
}
}
.content-header {
.date {
font-weight: bold ;
}
}
#app-distribution-index {
position: relative ;
}
#distribution-date {
display: none ;
}
#loading {
position: absolute ;
top: -60px ;
left: 50% ;
margin-left: -15px ;
img {
width: 30px ;
height: 30px ;
}
}
#calendar {
margin-bottom: 15px ;
.c-header .c-title-layout .c-title-popover .c-title-anchor .c-title[data-v-2083cb72] {
font-size: 2rem ;
}
.c-day-background {
//background-color: #F39C12;
//background-color: white ;
padding: 16px ;
//border: solid 1px black !important ;
//opacity: 1 ;
}
.c-day-popover-content {
font-size: 1.3rem ;
}
}
#products {
td.quantities {
width: 100px;
text-align: right ;
}
input.quantity-max {
width: 50px ;
text-align: center ;
display: inline ;
}
}
#infos-top {
.col-md-4 {
padding: 0px ;
}
$height-info-box: 96px ;
.info-box {
min-height: $height-info-box ;
height: $height-info-box ;
.info-box-icon {
height: $height-info-box ;
width: 50px ;
line-height: $height-info-box ;
i.fa {
font-size: 30px ;
}
}
.info-box-content {
margin-left: 55px ;
.info-box-text {
font-size: 12px ;
.btn {
font-size: 12px ;
text-transform: uppercase ;
}
}
.info-box-number {
font-size: 14px ;
}
}
}
}
#modal-products {
table.table {
thead {
tr {
td {
font-weight: bold ;
}
}
}
td.quantity-ordered,
td.quantity-max {
text-align: center;
}

td.quantity-ordered {
width: 50px ;
}
td.quantity-max {
width: 70px ;
input {
text-align: center ;
}
}
}
}
#orders {
#btn-add-order {
float: right ;
}
#wrapper-nav-points-sale {
margin-bottom: 10px ;
ul#nav-points-sale {
margin: 0px ;
padding: 0px ;
list-style-type: none ;

li {
float: left ;
margin-right: 10px ;
margin-bottom: 10px ;
a {
position: relative ;
&.btn-primary {
.glyphicon {
display: block ;
}
}
.glyphicon {
display: none ;
position: absolute ;
top: 26px ;
left: 50% ;
margin-left: -10px ;
font-size: 20px ;
color: $color1 ;
position: absolute ;
}
.label {
background-color: white ;
border: solid 1px #e0e0e0 ;
@include border-radius(10px) ;
}
}
}
}
}
table {
td.column-actions {
text-align: right ;
width: 200px ;
.modal-form-order,
.modal-payment {
text-align: left ;
}
}
tr.view {
ul {
list-style-type: none ;
margin-left: 0px ;
padding-left: 15px ;
li {
}
}
}
}
}
.modal-form-order {
table.table-products {
.product-ordered {
td {
background-color: #e9e9e9 ;
}

input {
font-size: 16px ;
font-weight: bold ;
}
}
td.quantity {
width: 150px ;
input {
text-align: center ;
color: gray ;
}
}
td.quantity-remaining {
text-align: right ;
}
}
}
.modal-payment {
.info-box {
.info-box-icon {
width: 50px ;
i {
font-size: 30px ;
}
}
.info-box-content {
margin-left: 50px ;
}
}
}
.modal-mask {
position: fixed;
z-index: 9998;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, .5);
display: table;
transition: opacity .3s ease;
}

.modal-wrapper {
display: table-cell;
vertical-align: middle;
}

.modal-container {
width: 70%;
margin: 0px auto;
padding: 20px 30px;
background-color: #fff;
border-radius: 2px;
box-shadow: 0 2px 8px rgba(0, 0, 0, .33);
transition: all .3s ease;
font-family: Helvetica, Arial, sans-serif;
}
.modal-header {
padding-bottom: 0px ;
h3 {
margin-top: 0;
color: #333;
text-transform: uppercase ;
margin-bottom: 0px ;
}
}

.modal-body {
margin: 20px 0;
max-height: 300px ;
height: 300px ;
overflow-y: scroll ;
}

.modal-default-button {
float: right;
}

/*
* The following styles are auto-applied to elements with
* transition="modal" when their visibility is toggled
* by Vue.js.
*
* You can easily play with the modal transition by editing
* these styles.
*/

.modal-enter {
opacity: 0;
}

.modal-leave-active {
opacity: 0;
}

.modal-enter .modal-container,
.modal-leave-active .modal-container {
-webkit-transform: scale(1.1);
transform: scale(1.1);
}

}


+ 3
- 14
backend/web/sass/screen.scss Ver fichero

@@ -85,8 +85,7 @@ a {
}

.wrap {
.btn-primary,
.btn-success {
.btn-primary {
background: none ;
background-color: $color1 ;
border: solid 1px $color1 ;
@@ -352,12 +351,6 @@ a {
float: right ;
font-family: 'myriadpro-regular' ;
&.btn-success {
background-color: #5cb85c ;
color: white ;
border-color: #5cb85c ;
}
&.margin-left {
margin-left: 5px ;
}
@@ -479,11 +472,6 @@ a {
font-size: 13px ;
}
.btn-success {
background-color: #5cb85c ;
border-color: #4cae4c ;
}
#productions-point-vente {
margin-top: 15px ;
padding: 10px ;
@@ -1347,4 +1335,5 @@ a {
@import "site/_index.scss" ;
@import "subscription/_index.scss" ;
@import "product/_index.scss" ;
@import "stats/_products.scss" ;
@import "stats/_products.scss" ;
@import "distribution/_index.scss" ;

+ 4
- 1
common/assets/CommonAsset.php Ver fichero

@@ -59,12 +59,15 @@ class CommonAsset extends \common\components\MyAssetBundle
// css
$this->addAsset('css','bootstrap/css/bootstrap.min.css') ;
$this->addAsset('css','bootstrap/css/bootstrap-theme.min.css');
$this->addAsset('css','js/jquery-ui-1.11.4.custom/jquery-ui.min.css');
$this->addAsset('css','js/jquery-ui-1.11.4.custom/jquery-ui.theme.css');
$this->addAsset('css','js/vuejs/vcalendar/vcalendar.min.css') ;
$this->addAsset('css','css/screen.css') ;
// js
$this->addAsset('js','js/jquery-ui-1.11.4.custom/jquery-ui.min.js');
$this->addAsset('js','js/axios/axios.min.js');
$this->addAsset('js','js/vuejs/vue.js');
$this->addAsset('js','js/vuejs/vcalendar/vcalendar.min.js') ;
}
}

+ 4
- 0
common/components/ActiveRecordCommon.php Ver fichero

@@ -143,6 +143,10 @@ class ActiveRecordCommon extends \yii\db\ActiveRecord
}
}
if(isset($options['as_array'])) {
$records = $records->asArray() ;
}
if($options['type_search'] == self::SEARCH_ALL) {
return $records->all();
}

+ 1
- 1
common/models/Distribution.php Ver fichero

@@ -48,7 +48,7 @@ use common\models\Distribution;
*
* @property integer $id
* @property string $date
* @property integer $actif
* @property integer $active
*/
class Distribution extends ActiveRecordCommon
{

+ 2
- 2
common/models/Order.php Ver fichero

@@ -152,7 +152,7 @@ class Order extends ActiveRecordCommon
*/
public static function defaultOptionsSearch() {
return [
'with' => ['productOrder', 'creditHistory', 'pointSale'],
'with' => ['productOrder', 'creditHistory','creditHistory.userAction' , 'pointSale'],
'join_with' => ['distribution', 'user', 'user.userProducer'],
'orderby' => 'order.date ASC',
'attribute_id_producer' => 'distribution.id_producer'
@@ -236,7 +236,7 @@ class Order extends ActiveRecordCommon
$amount = $this->getAmount(self::AMOUNT_TOTAL)
- $this->getAmount(self::AMOUNT_PAID) ;
break ;
case self::AMOUNT_EXCESS :
case self::AMOUNT_SURPLUS :
$amount = $this->getAmount(self::AMOUNT_PAID)
- $this->getAmount(self::AMOUNT_TOTAL) ;
break ;

+ 3
- 3
common/models/PointSaleDistribution.php Ver fichero

@@ -46,9 +46,9 @@ use common\models\Production;
/**
* This is the model class for table "production_point_vente".
*
* @property integer $id_production
* @property integer $id_point_vente
* @property integer $livraison
* @property integer $id_distribution
* @property integer $id_point_sale
* @property integer $delivery
*/
class PointSaleDistribution extends ActiveRecordCommon
{

+ 7
- 1
common/models/Product.php Ver fichero

@@ -56,7 +56,8 @@ use common\components\ActiveRecordCommon ;
class Product extends ActiveRecordCommon
{
var $total = 0;

var $test ;
const SALE_MODE_UNIT = 'unit' ;
const SALE_MODE_WEIGHT = 'weight' ;
@@ -111,6 +112,11 @@ class Product extends ActiveRecordCommon
];
}
public function getProductDistribution()
{
return $this->hasMany(ProductDistribution::className(), ['id_product' => 'id']);
}
/**
* Retourne les options de base nécessaires à la fonction de recherche.
*

+ 9
- 0
common/web/js/axios/axios.min.js
La diferencia del archivo ha sido suprimido porque es demasiado grande
Ver fichero


+ 3
- 0
common/web/js/vuejs/vcalendar/vcalendar.min.css
La diferencia del archivo ha sido suprimido porque es demasiado grande
Ver fichero


+ 2
- 0
common/web/js/vuejs/vcalendar/vcalendar.min.js
La diferencia del archivo ha sido suprimido porque es demasiado grande
Ver fichero


+ 11063
- 0
common/web/js/vuejs/vue.js
La diferencia del archivo ha sido suprimido porque es demasiado grande
Ver fichero


+ 6
- 0
common/web/js/vuejs/vue.min.js
La diferencia del archivo ha sido suprimido porque es demasiado grande
Ver fichero


+ 1
- 1
producer/controllers/OrderController.php Ver fichero

@@ -292,7 +292,7 @@ class OrderController extends ProducerBaseController
$order->load(Yii::$app->request->post());
$order->id_user = User::getCurrentId();
$order->date = date('Y-m-d H:i:s');
$order->type = Order::ORIGIN_USER;
$order->origin = Order::ORIGIN_USER;
}
$this->processForm($order);

Cargando…
Cancelar
Guardar