Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

745 lines
27KB

  1. <?php
  2. /**
  3. * Copyright distrib (2018)
  4. *
  5. * contact@opendistrib.net
  6. *
  7. * Ce logiciel est un programme informatique servant à aider les producteurs
  8. * à distribuer leur production en circuits courts.
  9. *
  10. * Ce logiciel est régi par la licence CeCILL soumise au droit français et
  11. * respectant les principes de diffusion des logiciels libres. Vous pouvez
  12. * utiliser, modifier et/ou redistribuer ce programme sous les conditions
  13. * de la licence CeCILL telle que diffusée par le CEA, le CNRS et l'INRIA
  14. * sur le site "http://www.cecill.info".
  15. *
  16. * En contrepartie de l'accessibilité au code source et des droits de copie,
  17. * de modification et de redistribution accordés par cette licence, il n'est
  18. * offert aux utilisateurs qu'une garantie limitée. Pour les mêmes raisons,
  19. * seule une responsabilité restreinte pèse sur l'auteur du programme, le
  20. * titulaire des droits patrimoniaux et les concédants successifs.
  21. *
  22. * A cet égard l'attention de l'utilisateur est attirée sur les risques
  23. * associés au chargement, à l'utilisation, à la modification et/ou au
  24. * développement et à la reproduction du logiciel par l'utilisateur étant
  25. * donné sa spécificité de logiciel libre, qui peut le rendre complexe à
  26. * manipuler et qui le réserve donc à des développeurs et des professionnels
  27. * avertis possédant des connaissances informatiques approfondies. Les
  28. * utilisateurs sont donc invités à charger et tester l'adéquation du
  29. * logiciel à leurs besoins dans des conditions permettant d'assurer la
  30. * sécurité de leurs systèmes et ou de leurs données et, plus généralement,
  31. * à l'utiliser et l'exploiter dans les mêmes conditions de sécurité.
  32. *
  33. * Le fait que vous puissiez accéder à cet en-tête signifie que vous avez
  34. * pris connaissance de la licence CeCILL, et que vous en avez accepté les
  35. * termes.
  36. */
  37. namespace backend\controllers;
  38. use common\models\DeliveryNote;
  39. use common\models\Invoice;
  40. use common\models\PointSale;
  41. use common\models\Product;
  42. use common\models\Quotation;
  43. use common\models\User;
  44. use common\models\Document;
  45. use common\helpers\GlobalParam;
  46. use common\models\Order;
  47. use common\models\UserProducer;
  48. use kartik\mpdf\Pdf;
  49. use yii\base\UserException;
  50. use yii;
  51. class DocumentController extends BackendController
  52. {
  53. public function behaviors()
  54. {
  55. return [
  56. 'verbs' => [
  57. 'class' => VerbFilter::className(),
  58. 'actions' => [
  59. ],
  60. ],
  61. 'access' => [
  62. 'class' => AccessControl::className(),
  63. 'rules' => [
  64. [
  65. 'allow' => true,
  66. 'roles' => ['@'],
  67. 'matchCallback' => function ($rule, $action) {
  68. return User::hasAccessBackend();
  69. }
  70. ]
  71. ],
  72. ],
  73. ];
  74. }
  75. public function actionGeneratePdfValidatedDocuments()
  76. {
  77. set_time_limit(0);
  78. $validatedDocumentsArray = array_merge(
  79. Quotation::find()->where(['status' => Document::STATUS_VALID])->all(),
  80. DeliveryNote::find()->where(['status' => Document::STATUS_VALID])->all(),
  81. Invoice::find()->where(['status' => Document::STATUS_VALID])->all()
  82. );
  83. foreach($validatedDocumentsArray as $document) {
  84. if(!file_exists($document->getFilenameComplete())) {
  85. $document->generatePdf(Pdf::DEST_FILE);
  86. }
  87. }
  88. }
  89. public function actionCreate()
  90. {
  91. $class = $this->getClass();
  92. $model = new $class();
  93. $model->initTaxCalculationMethod();
  94. if ($model->load(Yii::$app->request->post())) {
  95. $model->id_producer = GlobalParam::getCurrentProducerId();
  96. if ($model->save()) {
  97. $this->processInvoiceViaDeliveryNotes($model);
  98. Yii::$app->getSession()->setFlash('success', $this->getFlashMessage('create', $model));
  99. return $this->redirect(['/' . $this->getControllerUrl() . '/update', 'id' => $model->id]);
  100. } else {
  101. Yii::$app->getSession()->setFlash('error', 'Un problème est survenu lors de la création du document.');
  102. }
  103. }
  104. return $this->render('/document/create', [
  105. 'title' => $this->getTitle('Ajouter'),
  106. 'typeDocument' => $this->getDocumentType(),
  107. 'model' => $model,
  108. ]);
  109. }
  110. public function processInvoiceViaDeliveryNotes($model)
  111. {
  112. if ($model->getClass() == 'Invoice') {
  113. if ($model->deliveryNotes && is_array($model->deliveryNotes) && count($model->deliveryNotes)) {
  114. foreach ($model->deliveryNotes as $key => $idDeliveryNote) {
  115. Order::updateAll([
  116. 'id_invoice' => $model->id
  117. ], [
  118. 'id_delivery_note' => $idDeliveryNote
  119. ]);
  120. }
  121. }
  122. }
  123. }
  124. /**
  125. * Modifie un modèle Produit existant.
  126. * Si la modification réussit, le navigateur est redirigé vers la page 'index'.
  127. *
  128. * @param integer $id
  129. * @return mixed
  130. */
  131. public function actionUpdate($id)
  132. {
  133. $model = $this->findModel($id);
  134. if (!$model) {
  135. throw new NotFoundHttpException('Le document n\'a pas été trouvé.');
  136. }
  137. if ($model && $model->load(Yii::$app->request->post()) && $model->save()) {
  138. Yii::$app->getSession()->setFlash('success', $this->getFlashMessage('update', $model));
  139. }
  140. return $this->render('/document/update', [
  141. 'title' => $this->getTitle('Modifier'),
  142. 'typeDocument' => $this->getDocumentType(),
  143. 'model' => $model,
  144. ]);
  145. }
  146. public function actionDelete($id)
  147. {
  148. $model = $this->findModel($id);
  149. if ($model->isStatusValid()) {
  150. throw new UserException('Vous ne pouvez pas supprimer un document validé.');
  151. }
  152. $model->delete();
  153. if ($this->getClass() == 'DeliveryNote') {
  154. Order::updateAll([
  155. 'order.id_delivery_note' => null
  156. ], [
  157. 'order.id_delivery_note' => $id
  158. ]);
  159. }
  160. if ($this->getClass() == 'Quotation') {
  161. Order::updateAll([
  162. 'order.id_quotation' => null
  163. ], [
  164. 'order.id_quotation' => $id
  165. ]);
  166. }
  167. if ($this->getClass() == 'Invoice') {
  168. Order::updateAll([
  169. 'order.id_invoice' => null
  170. ], [
  171. 'order.id_invoice' => $id
  172. ]);
  173. }
  174. Yii::$app->getSession()->setFlash('success', $this->getFlashMessage('delete', $model));
  175. $this->redirect([$this->getControllerUrl() . '/index']);
  176. }
  177. public function actionExportCsvEvoliz($id)
  178. {
  179. $datas = [];
  180. $document = $this->findModel($id);
  181. // données
  182. $datas[] = [
  183. 'N° facture externe *',
  184. 'Date facture *',
  185. 'Client',
  186. 'Code client *',
  187. 'Total TVA',
  188. 'Total HT',
  189. 'Total TTC',
  190. 'Total réglé',
  191. 'Etat',
  192. 'Date Etat',
  193. 'Date de création',
  194. 'Objet',
  195. 'Date d\'échéance',
  196. 'Date d\'exécution',
  197. 'Taux de pénalité',
  198. 'Frais de recouvrement',
  199. 'Taux d\'escompte',
  200. 'Conditions de règlement *',
  201. 'Mode de paiement',
  202. 'Remise globale',
  203. 'Acompte',
  204. 'Nombre de relance',
  205. 'Commentaires',
  206. 'N° facture',
  207. 'Annulé',
  208. 'Catalogue',
  209. 'Réf.',
  210. 'Désignation *',
  211. 'Qté *',
  212. 'Unité',
  213. 'PU HT *',
  214. 'Remise',
  215. 'TVA',
  216. 'Total TVA',
  217. 'Total HT',
  218. 'Créateur',
  219. ];
  220. foreach($document->getProductsOrders() as $productOrderArray) {
  221. foreach($productOrderArray as $productOrder) {
  222. $price = $productOrder->getPrice() ;
  223. if($document->isInvoicePrice() && $productOrder->getInvoicePrice()) {
  224. $price = $productOrder->getInvoicePrice() ;
  225. }
  226. $datas[] = [
  227. $document->reference, // N° facture externe *
  228. date('d/m/Y', strtotime($document->date)), // Date facture *
  229. '', // Client
  230. $document->user->evoliz_code, // Code client *
  231. '', // Total TVA
  232. '', // Total HT
  233. '', // Total TTC
  234. '', // Total réglé
  235. '', // Etat
  236. '', // Date Etat
  237. '', // Date de création
  238. $document->name, // Objet
  239. '', // Date d'échéance
  240. '', // Date d'exécution
  241. '', // Taux de pénalité
  242. '', // Frais de recouvrement
  243. '', // Taux d\'escompte
  244. 'A réception', // Conditions de règlement *
  245. '', // Mode de paiement
  246. '', // Remise globale
  247. '', // Acompte
  248. '', // Nombre de relance
  249. '', // Commentaires
  250. '', // N° facture
  251. '', // Annulé
  252. 'Non', // Catalogue
  253. '', // Réf.
  254. $productOrder->product->name, // Désignation *
  255. $productOrder->quantity, // Qté *
  256. '', // Product::strUnit($productOrder->unit, 'wording'), // Unité
  257. $price, // PU HT *
  258. '', // Remise
  259. $productOrder->taxRate->value * 100, // TVA
  260. '', // Total TVA
  261. '', // Total HT
  262. '', // Créateur
  263. ];
  264. }
  265. }
  266. // nom fichier
  267. $reference = $document->id;
  268. if($document->reference && strlen($document->reference)) {
  269. $reference = $document->reference;
  270. }
  271. // status
  272. $status = '';
  273. if($document->isStatusDraft()) {
  274. $status = 'brouillon_';
  275. }
  276. CSV::downloadSendHeaders(strtolower($this->getDocumentType()).'_' . $status . $reference . '.csv');
  277. echo CSV::array2csv($datas);
  278. die();
  279. }
  280. public function actionDownload($id)
  281. {
  282. $document = $this->findModel($id);
  283. return $document->downloadPdf();
  284. }
  285. public function actionSend($id, $backUpdateForm = false)
  286. {
  287. $document = $this->findModel($id);
  288. if ($document->send()) {
  289. $document->is_sent = true;
  290. $document->save();
  291. Yii::$app->getSession()->setFlash('success', $this->getFlashMessage('send', $document));
  292. } else {
  293. Yii::$app->getSession()->setFlash('danger', $this->getFlashMessage('send', $document));
  294. }
  295. if($backUpdateForm) {
  296. return $this->redirect([$this->getControllerUrl() . '/update', 'id' => $id]);
  297. }
  298. else {
  299. return $this->redirect([$this->getControllerUrl() . '/index']);
  300. }
  301. }
  302. public function actionAjaxUserInfos($typeAction, $idUser, $classDocument, $idDocument = false)
  303. {
  304. \Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
  305. if ($idUser > 0) {
  306. $user = User::searchOne([
  307. 'id' => $idUser
  308. ]);
  309. if ($user) {
  310. $document = null;
  311. if (Document::isValidClass($classDocument)) {
  312. $document = $classDocument::searchOne([
  313. 'id' => $idDocument,
  314. 'id_user' => $idUser
  315. ]);
  316. }
  317. if ($document && $document->id_user == $user->id) {
  318. $address = $document->address;
  319. } else {
  320. $address = $user->getFullAddress();
  321. }
  322. $json = [
  323. 'return' => 'success',
  324. 'address' => $address
  325. ];
  326. if ($classDocument == 'Invoice') {
  327. if ($typeAction == 'create') {
  328. $deliveryNotesArray = DeliveryNote::searchAll([
  329. 'id_user' => $user->id,
  330. 'status' => Document::STATUS_VALID
  331. ]);
  332. } elseif ($typeAction == 'update' && $idDocument > 0) {
  333. $deliveryNotesArray = DeliveryNote::searchAll([
  334. 'id_user' => $user->id,
  335. 'status' => Document::STATUS_VALID,
  336. 'order.id_invoice' => $idDocument
  337. ]);
  338. }
  339. if (isset($deliveryNotesArray)) {
  340. $json['delivery_notes'] = [];
  341. foreach ($deliveryNotesArray as $deliveryNote) {
  342. if (!$deliveryNote->isInvoiced()) {
  343. $json['delivery_notes'][] = array_merge(
  344. $deliveryNote->getAttributes(),
  345. [
  346. 'total' => $deliveryNote->getAmountWithTax(Order::INVOICE_AMOUNT_TOTAL)
  347. ]
  348. );
  349. }
  350. }
  351. }
  352. }
  353. return $json;
  354. }
  355. }
  356. return ['return' => 'error'];
  357. }
  358. public function actionValidate($id, $backUpdateForm = false)
  359. {
  360. $classDocument = $this->getClass();
  361. if ($id > 0 && Document::isValidClass($classDocument)) {
  362. $document = $classDocument::searchOne([
  363. 'id' => $id
  364. ]);
  365. if ($document) {
  366. $document->changeStatus(Document::STATUS_VALID);
  367. $document->save();
  368. // génération PDF
  369. $document->generatePdf(Pdf::DEST_FILE);
  370. Yii::$app->getSession()->setFlash('success', $this->getFlashMessage('validate', $document));
  371. if($backUpdateForm) {
  372. return $this->redirect([$this->getControllerUrl() . '/update', 'id' => $id]);
  373. }
  374. else {
  375. return $this->redirect([$this->getControllerUrl() . '/index']);
  376. }
  377. }
  378. }
  379. Yii::$app->getSession()->setFlash('danger', 'Une erreur est survenue lors de la validation du document.');
  380. return $this->redirect([$this->getControllerUrl() . '/index']);
  381. }
  382. public function actionAjaxValidateDocument($idDocument, $classDocument)
  383. {
  384. \Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
  385. if ($idDocument > 0 && Document::isValidClass($classDocument)) {
  386. $document = $classDocument::searchOne([
  387. 'id' => $idDocument
  388. ]);
  389. if ($document) {
  390. $document->changeStatus(Document::STATUS_VALID);
  391. $document->save();
  392. return [
  393. 'return' => 'success',
  394. 'alert' => [
  395. 'type' => 'success',
  396. 'message' => 'Document validé'
  397. ]
  398. ];
  399. }
  400. }
  401. return [
  402. 'return' => 'error',
  403. 'alert' => [
  404. 'type' => 'danger',
  405. 'message' => 'Une erreur est survenue lors de la validation du document.'
  406. ]
  407. ];
  408. }
  409. public function actionAjaxInit($idDocument, $classDocument)
  410. {
  411. \Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
  412. if ($idDocument > 0 && Document::isValidClass($classDocument)) {
  413. $document = $classDocument::searchOne([
  414. 'id' => $idDocument
  415. ]);
  416. if ($document) {
  417. $productsArray = Product::searchAll([], [
  418. 'orderby' => 'product.order ASC'
  419. ]);
  420. $ordersArray = [];
  421. foreach ($document->orders as $order) {
  422. $order->init();
  423. $productsOrderArray = [];
  424. foreach ($order->productOrder as $productOrder) {
  425. $productsOrderArray[$productOrder->id] = array_merge($productOrder->getAttributes(), [
  426. 'url_order' => Yii::$app->urlManager->createUrl(['distribution/index', 'idOrderUpdate' => $productOrder->id_order])
  427. ]);
  428. }
  429. $ordersArray[$order->id] = array_merge(
  430. $order->getAttributes(),
  431. [
  432. 'username' => $order->getUsername(),
  433. 'distribution_date' => isset($order->distribution) ? date(
  434. 'd/m/Y',
  435. strtotime(
  436. $order->distribution->date
  437. )
  438. ) : null,
  439. 'point_sale_name' => isset($order->pointSale) ? $order->pointSale->name : null,
  440. 'productOrder' => $productsOrderArray,
  441. ]
  442. );
  443. }
  444. $userProducer = UserProducer::searchOne([
  445. 'id_user' => $document->user->id,
  446. 'id_producer' => GlobalParam::getCurrentProducerId()
  447. ]);
  448. $pointSale = PointSale::searchOne([
  449. 'id_user' => $document->user->id
  450. ]);
  451. $productsArray = yii\helpers\ArrayHelper::map(
  452. $productsArray,
  453. 'order',
  454. function ($product) use ($document, $userProducer, $pointSale) {
  455. return array_merge($product->getAttributes(), [
  456. 'unit_coefficient' => Product::$unitsArray[$product->unit]['coefficient'],
  457. 'prices' => $product->getPriceArray($userProducer->user, $pointSale),
  458. 'wording_unit' => $product->wording_unit,
  459. 'tax_rate' => $product->taxRate->value
  460. ]);
  461. }
  462. );
  463. return [
  464. 'return' => 'success',
  465. 'tax_rate_producer' => GlobalParam::getCurrentProducer()->taxRate->value,
  466. 'document' => array_merge($document->getAttributes(), [
  467. 'html_label' => $document->getHtmlLabel(),
  468. 'class' => $document->getClass()
  469. ]),
  470. 'id_user' => $document->user->id,
  471. 'products' => $productsArray,
  472. 'orders' => $ordersArray,
  473. 'total' => ($document->getClass() == 'Invoice' || $document->getClass(
  474. ) == 'DeliveryNote') ? $document->getAmount(
  475. Order::INVOICE_AMOUNT_TOTAL
  476. ) : $document->getAmount(Order::AMOUNT_TOTAL),
  477. 'total_with_tax' => ($document->getClass() == 'Invoice' || $document->getClass(
  478. ) == 'DeliveryNote') ? $document->getAmountWithTax(
  479. Order::INVOICE_AMOUNT_TOTAL
  480. ) : $document->getAmountWithTax(Order::AMOUNT_TOTAL),
  481. ];
  482. }
  483. }
  484. return ['return' => 'error'];
  485. }
  486. public function actionAjaxAddProduct($idDocument, $classDocument, $idProduct, $quantity, $price)
  487. {
  488. \Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
  489. if (Document::isValidClass($classDocument)) {
  490. $document = $classDocument::searchOne([
  491. 'id' => $idDocument
  492. ]);
  493. $product = Product::searchOne([
  494. 'id' => $idProduct
  495. ]);
  496. if ($document && $product) {
  497. if (count($document->orders) == 0) {
  498. $order = new Order;
  499. $order->id_user = $document->id_user;
  500. $order->id_point_sale = null;
  501. $order->id_distribution = null;
  502. $order->status = 'tmp-order';
  503. $order->origin = Order::ORIGIN_ADMIN;
  504. $order->date = date('Y-m-d H:i:s');
  505. $fieldIdDocument = 'id_' . $classDocument::tableName();
  506. $order->$fieldIdDocument = $document->id;
  507. $order->save();
  508. } else {
  509. $order = $document->orders[0];
  510. }
  511. if ($order) {
  512. $productOrder = new ProductOrder;
  513. $productOrder->id_order = $order->id;
  514. $productOrder->id_product = $idProduct;
  515. $quantity = $quantity / Product::$unitsArray[$product->unit]['coefficient'];
  516. $productOrder->quantity = $quantity;
  517. $productOrder->price = (float)$price;
  518. $productOrder->unit = $product->unit;
  519. $productOrder->step = $product->step;
  520. $productOrder->id_tax_rate = $product->taxRate->id;
  521. $productOrder->save();
  522. return [
  523. 'return' => 'success',
  524. 'alert' => [
  525. 'type' => 'success',
  526. 'message' => 'Produit ajouté'
  527. ]
  528. ];
  529. }
  530. }
  531. }
  532. return [
  533. 'return' => 'error',
  534. 'alert' => [
  535. 'type' => 'danger',
  536. 'message' => 'Une erreur est survenue lors de la suppression du produit.'
  537. ]
  538. ];
  539. }
  540. public function actionAjaxDeleteProductOrder($idProductOrder)
  541. {
  542. \Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
  543. $productOrder = ProductOrder::searchOne([
  544. 'id' => $idProductOrder
  545. ]);
  546. if ($productOrder) {
  547. $productOrder->delete();
  548. return [
  549. 'return' => 'success',
  550. 'alert' => [
  551. 'type' => 'danger',
  552. 'message' => 'Produit supprimé'
  553. ]
  554. ];
  555. }
  556. return [
  557. 'return' => 'error',
  558. 'alert' => [
  559. 'type' => 'danger',
  560. 'message' => 'Une erreur est survenue lors de la suppression du produit.'
  561. ]
  562. ];
  563. }
  564. public function getClass()
  565. {
  566. $class = get_class($this);
  567. $class = str_replace('Controller', '', $class);
  568. $class = str_replace('backend\controllers\\', '', $class);
  569. return $class;
  570. }
  571. public function getDocumentType()
  572. {
  573. $class = $this->getClass();
  574. if ($class == 'Invoice') {
  575. $documentType = 'Facture';
  576. } elseif ($class == 'DeliveryNote') {
  577. $documentType = 'Bon de livraison';
  578. } elseif ($class == 'Quotation') {
  579. $documentType = 'Devis';
  580. }
  581. if (isset($documentType)) {
  582. return $documentType;
  583. }
  584. return '';
  585. }
  586. public function getFlashMessage($type = 'create', $model)
  587. {
  588. $class = $this->getClass();
  589. $message = $this->getDocumentType();
  590. $message .= ' <strong>' . Html::encode($model->name) . '</strong> ';
  591. if ($type == 'create') {
  592. $message .= 'ajouté';
  593. } elseif ($type == 'update') {
  594. $message .= 'modifié';
  595. } elseif ($type == 'delete') {
  596. $message .= 'supprimé';
  597. } elseif ($type == 'validate') {
  598. $message .= 'validé';
  599. } elseif ($type == 'send') {
  600. $message .= 'envoyé';
  601. }
  602. if ($class == 'Invoice') {
  603. $message .= 'e';
  604. }
  605. return $message;
  606. }
  607. protected function getTitle($prepend)
  608. {
  609. $class = $this->getClass();
  610. switch ($class) {
  611. case 'Invoice' :
  612. $title = $prepend . ' une facture';
  613. break;
  614. case 'DeliveryNote' :
  615. $title = $prepend . ' un bon de livraison';
  616. break;
  617. case 'Quotation' :
  618. $title = $prepend . ' un devis';
  619. break;
  620. }
  621. return $title;
  622. }
  623. public function getControllerUrl()
  624. {
  625. $path = strtolower($this->getClass());
  626. $path = str_replace('deliverynote', 'delivery-note', $path);
  627. return $path;
  628. }
  629. /**
  630. * Recherche un Document en fonction de son ID.
  631. *
  632. * @param integer $id
  633. * @return Document
  634. * @throws NotFoundHttpException si le modèle n'est pas trouvé
  635. */
  636. protected function findModel($id)
  637. {
  638. $class = $this->getClass();
  639. $model = $class::searchOne([
  640. 'id' => $id
  641. ], [
  642. 'orderby' => 'teshtygjhtyt'
  643. ]);
  644. if ($model) {
  645. return $model;
  646. } else {
  647. throw new NotFoundHttpException('The requested page does not exist.');
  648. }
  649. }
  650. }