@@ -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') ; | |||
} | |||
} |
@@ -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'] ; | |||
} | |||
} |
@@ -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']; | |||
} | |||
} |
@@ -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="∞" :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> > | |||
<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> |
@@ -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 . ' ' . $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 .' '. $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 . ' '.$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 . ' '.$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 ; | |||
?> |
@@ -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()], | |||
[ |
@@ -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"> | |||
@@ -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> |
@@ -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'}) ; | |||
} | |||
@@ -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 ; | |||
} | |||
} | |||
} | |||
}) ; |
@@ -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 ; | |||
} |
@@ -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); | |||
} | |||
} | |||
@@ -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" ; |
@@ -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') ; | |||
} | |||
} |
@@ -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(); | |||
} |
@@ -48,7 +48,7 @@ use common\models\Distribution; | |||
* | |||
* @property integer $id | |||
* @property string $date | |||
* @property integer $actif | |||
* @property integer $active | |||
*/ | |||
class Distribution extends ActiveRecordCommon | |||
{ |
@@ -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 ; |
@@ -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 | |||
{ |
@@ -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. | |||
* |
@@ -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); |