Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

737 rindas
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 common\models;
  38. use Yii;
  39. use yii\helpers\Html;
  40. use common\models\Producer;
  41. use common\components\ActiveRecordCommon;
  42. /**
  43. * This is the model class for table "order".
  44. *
  45. * @property integer $id
  46. * @property integer $id_user
  47. * @property string $date
  48. * @property string $date_update
  49. * @property integer $id_point_sale
  50. * @property integer $id_distribution
  51. * @property boolean $auto_payment
  52. * @property integer $id_subscription
  53. */
  54. class Order extends ActiveRecordCommon
  55. {
  56. var $amount = 0;
  57. var $paid_amount = 0;
  58. var $weight = 0;
  59. const ORIGIN_AUTO = 'auto';
  60. const ORIGIN_USER = 'user';
  61. const ORIGIN_ADMIN = 'admin';
  62. const PAYMENT_PAID = 'paid';
  63. const PAYMENT_UNPAID = 'unpaid';
  64. const PAYMENT_SURPLUS = 'surplus';
  65. const AMOUNT_TOTAL = 'total';
  66. const AMOUNT_PAID = 'paid';
  67. const AMOUNT_REMAINING = 'remaining';
  68. const AMOUNT_SURPLUS = 'surplus';
  69. const STATE_OPEN = 'open';
  70. const STATE_PREPARATION = 'preparation';
  71. const STATE_DELIVERED = 'delivered';
  72. /**
  73. * @inheritdoc
  74. */
  75. public static function tableName()
  76. {
  77. return 'order';
  78. }
  79. /**
  80. * @inheritdoc
  81. */
  82. public function rules()
  83. {
  84. return [
  85. [['id_user', 'date', 'id_point_sale', 'id_distribution'], 'required', 'message' => ''],
  86. [['id_user', 'id_point_sale', 'id_distribution', 'id_subscription', 'id_order_status', 'id_invoice', 'id_quotation', 'id_delivery_note'], 'integer'],
  87. [['auto_payment', 'tiller_synchronization'], 'boolean'],
  88. [['date', 'date_update', 'comment', 'comment_point_sale', 'mean_payment'], 'safe']
  89. ];
  90. }
  91. /**
  92. * @inheritdoc
  93. */
  94. public function attributeLabels()
  95. {
  96. return [
  97. 'id' => 'ID',
  98. 'id_user' => 'Id User',
  99. 'date' => 'Date',
  100. 'date_update' => 'Date de modification',
  101. 'id_point_sale' => 'Point de vente',
  102. 'id_distribution' => 'Date de distribution',
  103. 'id_subscription' => 'Abonnement',
  104. 'id_order_status' => 'Statut',
  105. 'id_invoice' => 'Facture',
  106. 'id_quotation' => 'Devis',
  107. 'id_delivery_note' => 'Bon de livraison'
  108. ];
  109. }
  110. /*
  111. * Relations
  112. */
  113. public function getUser()
  114. {
  115. return $this->hasOne(User::className(), ['id' => 'id_user']);
  116. }
  117. public function getProductOrder()
  118. {
  119. return $this->hasMany(ProductOrder::className(), ['id_order' => 'id'])
  120. ->with('product');
  121. }
  122. public function getDistribution()
  123. {
  124. return $this->hasOne(Distribution::className(), ['id' => 'id_distribution'])
  125. ->with('producer');
  126. }
  127. public function getPointSale()
  128. {
  129. return $this->hasOne(PointSale::className(), ['id' => 'id_point_sale'])
  130. ->with('userPointSale');
  131. }
  132. public function getCreditHistory()
  133. {
  134. return $this->hasMany(CreditHistory::className(), ['id_order' => 'id']);
  135. }
  136. public function getSubscription()
  137. {
  138. return $this->hasOne(Subscription::className(), ['id' => 'id_subscription'])
  139. ->with('productSubscription');
  140. }
  141. public function getOrderOrderStatus()
  142. {
  143. return $this->hasMany(OrderOrderStatus::className(), ['id_order' => 'id'])->with('orderStatus');
  144. }
  145. public function getInvoice()
  146. {
  147. return $this->hasOne(Invoice::className(), ['id' => 'id_invoice']);
  148. }
  149. public function getQuotation()
  150. {
  151. return $this->hasOne(Quotation::className(), ['id' => 'id_quotation']);
  152. }
  153. public function getDeliveryNote()
  154. {
  155. return $this->hasOne(DeliveryNote::className(), ['id' => 'id_delivery_note']);
  156. }
  157. /**
  158. * Retourne les options de base nécessaires à la fonction de recherche.
  159. *
  160. * @return array
  161. */
  162. public static function defaultOptionsSearch()
  163. {
  164. return [
  165. 'with' => ['productOrder', 'productOrder.product', 'creditHistory', 'creditHistory.userAction', 'pointSale'],
  166. 'join_with' => ['distribution', 'user', 'user.userProducer'],
  167. 'orderby' => 'order.date ASC',
  168. 'attribute_id_producer' => 'distribution.id_producer'
  169. ];
  170. }
  171. /**
  172. * Initialise le montant total, le montant déjà payé et le poids de la
  173. * commande.
  174. */
  175. public function init()
  176. {
  177. $this->initAmount();
  178. $this->initPaidAmount();
  179. return $this;
  180. }
  181. /**
  182. * Initialise le montant de la commande.
  183. *
  184. */
  185. public function initAmount()
  186. {
  187. if (isset($this->productOrder)) {
  188. foreach ($this->productOrder as $productOrder) {
  189. $this->amount += $productOrder->price * $productOrder->quantity;
  190. if ($productOrder->unit == 'piece') {
  191. if (isset($productOrder->product)) {
  192. $this->weight += ($productOrder->quantity * $productOrder->product->weight) / 1000;
  193. }
  194. } else {
  195. $this->weight += $productOrder->quantity;
  196. }
  197. }
  198. }
  199. }
  200. /**
  201. * Initialise le montant payé de la commande et le retourne.
  202. *
  203. * @return float
  204. */
  205. public function initPaidAmount()
  206. {
  207. if (isset($this->creditHistory)) {
  208. $history = $this->creditHistory;
  209. } else {
  210. $history = CreditHistory::find()
  211. ->where(['id_order' => $this->id])
  212. ->all();
  213. }
  214. $this->paid_amount = 0;
  215. if (count($history)) {
  216. foreach ($history as $ch) {
  217. if ($ch->type == CreditHistory::TYPE_PAYMENT) {
  218. $this->paid_amount += $ch->amount;
  219. } elseif ($ch->type == CreditHistory::TYPE_REFUND) {
  220. $this->paid_amount -= $ch->amount;
  221. }
  222. }
  223. }
  224. }
  225. public function delete()
  226. {
  227. // remboursement si l'utilisateur a payé pour cette commande
  228. $amountPaid = $this->getAmount(Order::AMOUNT_PAID);
  229. if ($amountPaid > 0.01) {
  230. $this->saveCreditHistory(
  231. CreditHistory::TYPE_REFUND,
  232. $amountPaid,
  233. GlobalParam::getCurrentProducerId(),
  234. $this->id_user,
  235. User::getCurrentId()
  236. );
  237. }
  238. // delete
  239. if (Producer::getConfig('option_behavior_cancel_order') == Producer::BEHAVIOR_DELETE_ORDER_DELETE ||
  240. (Producer::getConfig('option_behavior_cancel_order') == Producer::BEHAVIOR_DELETE_ORDER_STATUS && strlen($this->date_delete))) {
  241. ProductOrder::deleteAll(['id_order' => $this->id]);
  242. return parent::delete();
  243. } // status 'delete'
  244. elseif (Producer::getConfig('option_behavior_cancel_order') == Producer::BEHAVIOR_DELETE_ORDER_STATUS) {
  245. $this->date_delete = date('Y-m-d H:i:s');
  246. return $this->save();
  247. }
  248. }
  249. /**
  250. * Retourne le montant de la commande (total, payé, restant, ou en surplus).
  251. *
  252. * @param boolean $format
  253. * @return float
  254. */
  255. public function getAmount($type = self::AMOUNT_TOTAL, $format = false)
  256. {
  257. switch ($type) {
  258. case self::AMOUNT_TOTAL :
  259. $amount = $this->amount;
  260. break;
  261. case self::AMOUNT_PAID :
  262. $this->initPaidAmount();
  263. $amount = $this->paid_amount;
  264. break;
  265. case self::AMOUNT_REMAINING :
  266. $amount = $this->getAmount(self::AMOUNT_TOTAL)
  267. - $this->getAmount(self::AMOUNT_PAID);
  268. break;
  269. case self::AMOUNT_SURPLUS :
  270. $amount = $this->getAmount(self::AMOUNT_PAID)
  271. - $this->getAmount(self::AMOUNT_TOTAL);
  272. break;
  273. }
  274. if ($format) {
  275. return number_format($amount, 2) . ' €';
  276. } else {
  277. return $amount;
  278. }
  279. }
  280. /**
  281. * Retourne les informations relatives à la commande au format JSON.
  282. *
  283. * @return string
  284. */
  285. public function getDataJson()
  286. {
  287. $order = Order::searchOne(['order.id' => $this->id]);
  288. $jsonOrder = [];
  289. if ($order) {
  290. $jsonOrder = [
  291. 'products' => [],
  292. 'amount' => $order->amount,
  293. 'str_amount' => $order->getAmount(self::AMOUNT_TOTAL, true),
  294. 'paid_amount' => $order->getAmount(self::AMOUNT_PAID),
  295. 'comment' => $order->comment,
  296. ];
  297. foreach ($order->productOrder as $productOrder) {
  298. $jsonOrder['products'][$productOrder->id_product] = $productOrder->quantity;
  299. }
  300. }
  301. return json_encode($jsonOrder);
  302. }
  303. /**
  304. * Enregistre un modèle de type CreditHistory.
  305. *
  306. * @param string $type
  307. * @param float $montant
  308. * @param integer $idProducer
  309. * @param integer $idUser
  310. * @param integer $idUserAction
  311. */
  312. public function saveCreditHistory($type, $amount, $idProducer, $idUser, $idUserAction)
  313. {
  314. $creditHistory = new CreditHistory;
  315. $creditHistory->id_user = $this->id_user;
  316. $creditHistory->id_order = $this->id;
  317. $creditHistory->amount = $amount;
  318. $creditHistory->type = $type;
  319. $creditHistory->id_producer = $idProducer;
  320. $creditHistory->id_user_action = $idUserAction;
  321. $creditHistory->populateRelation('order', $this);
  322. $creditHistory->populateRelation('user', User::find()->where(['id' => $this->id_user])->one());
  323. $creditHistory->save();
  324. }
  325. /**
  326. * Ajuste le crédit pour que la commande soit payée.
  327. *
  328. * @return boolean
  329. */
  330. public function processCredit()
  331. {
  332. if ($this->id_user) {
  333. $paymentStatus = $this->getPaymentStatus();
  334. if ($paymentStatus == self::PAYMENT_PAID) {
  335. return true;
  336. } elseif ($paymentStatus == self::PAYMENT_SURPLUS) {
  337. $type = CreditHistory::TYPE_REFUND;
  338. $amount = $this->getAmount(self::AMOUNT_SURPLUS);
  339. } elseif ($paymentStatus == self::PAYMENT_UNPAID) {
  340. $type = CreditHistory::TYPE_PAYMENT;
  341. $amount = $this->getAmount(self::AMOUNT_REMAINING);
  342. }
  343. $this->saveCreditHistory(
  344. $type,
  345. $amount,
  346. GlobalParam::getCurrentProducerId(),
  347. $this->id_user,
  348. User::getCurrentId()
  349. );
  350. }
  351. }
  352. /**
  353. * Retourne le statut de paiement de la commande (payée, surplus, ou impayée).
  354. *
  355. * @return string
  356. */
  357. public function getPaymentStatus()
  358. {
  359. // payé
  360. if ($this->getAmount() - $this->getAmount(self::AMOUNT_PAID) < 0.01 &&
  361. $this->getAmount() - $this->getAmount(self::AMOUNT_PAID) > -0.01) {
  362. return self::PAYMENT_PAID;
  363. } // à rembourser
  364. elseif ($this->getAmount() - $this->getAmount(self::AMOUNT_PAID) <= -0.01) {
  365. return self::PAYMENT_SURPLUS;
  366. } // reste à payer
  367. elseif ($this->getAmount() - $this->getAmount(self::AMOUNT_PAID) >= 0.01) {
  368. return self::PAYMENT_UNPAID;
  369. }
  370. }
  371. /**
  372. * Retourne le résumé du panier au format HTML.
  373. *
  374. * @return string
  375. */
  376. public function getCartSummary()
  377. {
  378. if (!isset($this->productOrder)) {
  379. $this->productOrder = productOrder::find()->where(['id_order' => $this->id])->all();
  380. }
  381. $html = '';
  382. $count = count($this->productOrder);
  383. $i = 0;
  384. foreach ($this->productOrder as $p) {
  385. if (isset($p->product)) {
  386. $html .= Html::encode($p->product->name) . ' (' . $p->quantity . '&nbsp;' . Product::strUnit($p->unit, 'wording_short', true) . ')';
  387. if (++$i != $count) {
  388. $html .= '<br />';
  389. }
  390. }
  391. }
  392. return $html;
  393. }
  394. /**
  395. * Retourne le résumé du point de vente lié à la commande au format HTML.
  396. *
  397. * @return string
  398. */
  399. public function getPointSaleSummary()
  400. {
  401. $html = '';
  402. if (isset($this->pointSale)) {
  403. $html .= '<span class="name-point-sale">' .
  404. Html::encode($this->pointSale->name) .
  405. '</span>' .
  406. '<br /><span class="locality">'
  407. . Html::encode($this->pointSale->locality)
  408. . '</span>';
  409. if (strlen($this->comment_point_sale)) {
  410. $html .= '<div class="comment"><span>'
  411. . Html::encode($this->comment_point_sale)
  412. . '</span></div>';
  413. }
  414. } else {
  415. $html .= 'Point de vente supprimé';
  416. }
  417. return $html;
  418. }
  419. /**
  420. * Retourne le résumé du paiement (montant, statut).
  421. *
  422. * @return string
  423. */
  424. public function getAmountSummary()
  425. {
  426. $html = '';
  427. $html .= $this->getAmount(self::AMOUNT_TOTAL, true) . '<br />';
  428. if ($this->paid_amount) {
  429. if ($this->getPaymentStatus() == Order::PAYMENT_PAID) {
  430. $html .= '<span class="label label-success">Payée</span>';
  431. } elseif ($this->getPaymentStatus() == Order::PAYMENT_UNPAID) {
  432. $html .= '<span class="label label-danger">Non payée</span><br />
  433. Reste <strong>' . $this->getAmount(Order::AMOUNT_REMAINING, true) . '</strong> à payer';
  434. } elseif ($this->getPaymentStatus() == Order::PAYMENT_SURPLUS) {
  435. $html .= '<span class="label label-success">Payée</span>';
  436. }
  437. } else {
  438. $html .= '<span class="label label-default">Non réglé</span>';
  439. }
  440. return $html;
  441. }
  442. /**
  443. * Retourne une chaine de caractère décrivant l'utilisateur lié à la commande.
  444. *
  445. * @return string
  446. */
  447. public function getStrUser()
  448. {
  449. if (isset($this->user)) {
  450. return Html::encode($this->user->lastname . ' ' . $this->user->name);
  451. } elseif (strlen($this->username)) {
  452. return Html::encode($this->username);
  453. } else {
  454. return 'Client introuvable';
  455. }
  456. }
  457. /**
  458. * Retourne l'état de la commande (livrée, modifiable ou en préparation)
  459. *
  460. * @return string
  461. */
  462. public function getState()
  463. {
  464. $orderDelay = Producer::getConfig(
  465. 'order_delay',
  466. $this->distribution->id_producer
  467. );
  468. $orderDeadline = Producer::getConfig(
  469. 'order_deadline',
  470. $this->distribution->id_producer
  471. );
  472. $orderDate = strtotime($this->distribution->date);
  473. $today = strtotime(date('Y-m-d'));
  474. $todayHour = date('G');
  475. $nbDays = (int)(($orderDate - $today) / (24 * 60 * 60));
  476. if ($nbDays <= 0) {
  477. return self::STATE_DELIVERED;
  478. } elseif ($nbDays >= $orderDelay &&
  479. ($nbDays != $orderDelay ||
  480. ($nbDays == $orderDelay && $todayHour < $orderDeadline))) {
  481. return self::STATE_OPEN;
  482. }
  483. return self::STATE_PREPARATION;
  484. }
  485. /**
  486. * Retourne l'origine de la commande (client, automatique ou admin) sous forme
  487. * texte ou HTML.
  488. *
  489. * @param boolean $with_label
  490. * @return string
  491. */
  492. public function getStrOrigin($withLabel = false)
  493. {
  494. $classLabel = '';
  495. $str = '';
  496. if ($this->origin == self::ORIGIN_USER) {
  497. $classLabel = 'success';
  498. $str = 'Client';
  499. } elseif ($this->origin == self::ORIGIN_AUTO) {
  500. $classLabel = 'default';
  501. $str = 'Auto';
  502. } elseif ($this->origin == self::ORIGIN_ADMIN) {
  503. $classLabel = 'warning';
  504. $str = 'Vous';
  505. }
  506. if ($withLabel) {
  507. return '<span class="label label-' . $classLabel . '">'
  508. . $str . '</span>';
  509. } else {
  510. return $str;
  511. }
  512. }
  513. /**
  514. * Retourne l'historique de la commande (ajoutée, modifiée, supprimée) au
  515. * format HTML.
  516. *
  517. * @return string
  518. */
  519. public function getStrHistory()
  520. {
  521. $arr = [
  522. 'class' => 'create',
  523. 'glyphicon' => 'plus',
  524. 'str' => 'Ajoutée',
  525. 'date' => $this->date
  526. ];
  527. if (!is_null($this->date_update)) {
  528. $arr = [
  529. 'class' => 'update',
  530. 'glyphicon' => 'pencil',
  531. 'str' => 'Modifiée',
  532. 'date' => $this->date_update
  533. ];
  534. }
  535. if (!is_null($this->date_delete)) {
  536. $arr = [
  537. 'class' => 'delete',
  538. 'glyphicon' => 'remove',
  539. 'str' => 'Annulée',
  540. 'date' => $this->date_delete
  541. ];
  542. }
  543. $html = '<div class="small"><span class="' . $arr['class'] . '">'
  544. . '<span class="glyphicon glyphicon-' . $arr['glyphicon'] . '"></span> '
  545. . $arr['str'] . '</span> le <strong>'
  546. . date('d/m/Y à G\hi', strtotime($arr['date'])) . '</strong></div>';
  547. return $html;
  548. }
  549. /**
  550. * Retourne une classe identifiant l'historique de la commande (ajoutée,
  551. * modifiée, supprimée).
  552. *
  553. * @return string
  554. */
  555. public function getClassHistory()
  556. {
  557. if (!is_null($this->date_delete)) {
  558. return 'commande-delete';
  559. }
  560. if (!is_null($this->date_update)) {
  561. return 'commande-update';
  562. }
  563. return 'commande-create';
  564. }
  565. /**
  566. * Retourne la quantité d'un produit donné de plusieurs commandes.
  567. *
  568. * @param integer $idProduct
  569. * @param array $orders
  570. * @param boolean $ignoreCancel
  571. *
  572. * @return integer
  573. */
  574. public static function getProductQuantity($idProduct, $orders, $ignoreCancel = false, $unit = null)
  575. {
  576. $quantity = 0;
  577. if (isset($orders) && is_array($orders) && count($orders)) {
  578. foreach ($orders as $c) {
  579. if (is_null($c->date_delete) || $ignoreCancel) {
  580. foreach ($c->productOrder as $po) {
  581. if ($po->id_product == $idProduct &&
  582. ((is_null($unit) && $po->product->unit == $po->unit) || (!is_null($unit) && strlen($unit) && $po->unit == $unit))) {
  583. $quantity += $po->quantity;
  584. }
  585. }
  586. }
  587. }
  588. }
  589. return $quantity;
  590. }
  591. /**
  592. * Recherche et initialise des commandes.
  593. *
  594. * @param array $params
  595. * @param array $conditions
  596. * @param string $orderby
  597. * @param integer $limit
  598. * @return array
  599. */
  600. public static function searchBy($params = [], $options = [])
  601. {
  602. $orders = parent::searchBy($params, $options);
  603. /*
  604. * Initialisation des commandes
  605. */
  606. if (is_array($orders)) {
  607. if (count($orders)) {
  608. foreach ($orders as $order) {
  609. if (is_a($order, 'common\models\Order')) {
  610. $order->init();
  611. }
  612. }
  613. return $orders;
  614. }
  615. } else {
  616. $order = $orders;
  617. if (is_a($order, 'common\models\Order')) {
  618. return $order->init();
  619. } // count
  620. else {
  621. return $order;
  622. }
  623. }
  624. return false;
  625. }
  626. /**
  627. * Retourne le nombre de produits commandés
  628. *
  629. * @return integer
  630. */
  631. public function countProducts()
  632. {
  633. $count = 0;
  634. if ($this->productOrder && is_array($this->productOrder)) {
  635. foreach ($this->productOrder as $productOrder) {
  636. if ($productOrder->unit == 'piece') {
  637. $count++;
  638. } else {
  639. $count += $productOrder->quantity;
  640. }
  641. }
  642. }
  643. return $count;
  644. }
  645. /**
  646. * Retourne un bloc html présentant une date.
  647. *
  648. * @return string
  649. */
  650. public function getBlockDate()
  651. {
  652. return '<div class="block-date">
  653. <div class="day">' . strftime('%A', strtotime($this->distribution->date)) . '</div>
  654. <div class="num">' . date('d', strtotime($this->distribution->date)) . '</div>
  655. <div class="month">' . strftime('%B', strtotime($this->distribution->date)) . '</div>
  656. </div>';
  657. }
  658. }