PageRenderTime 114ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/classes/order/OrderHistory.php

https://gitlab.com/mtellezgalindo/PrestaShop
PHP | 505 lines | 355 code | 57 blank | 93 comment | 85 complexity | da536b2ada4845b550355950e602174a MD5 | raw file
Possible License(s): CC-BY-SA-3.0, LGPL-3.0
  1. <?php
  2. /*
  3. * 2007-2014 PrestaShop
  4. *
  5. * NOTICE OF LICENSE
  6. *
  7. * This source file is subject to the Open Software License (OSL 3.0)
  8. * that is bundled with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://opensource.org/licenses/osl-3.0.php
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@prestashop.com so we can send you a copy immediately.
  14. *
  15. * DISCLAIMER
  16. *
  17. * Do not edit or add to this file if you wish to upgrade PrestaShop to newer
  18. * versions in the future. If you wish to customize PrestaShop for your
  19. * needs please refer to http://www.prestashop.com for more information.
  20. *
  21. * @author PrestaShop SA <contact@prestashop.com>
  22. * @copyright 2007-2014 PrestaShop SA
  23. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  24. * International Registered Trademark & Property of PrestaShop SA
  25. */
  26. class OrderHistoryCore extends ObjectModel
  27. {
  28. /** @var integer Order id */
  29. public $id_order;
  30. /** @var integer Order status id */
  31. public $id_order_state;
  32. /** @var integer Employee id for this history entry */
  33. public $id_employee;
  34. /** @var string Object creation date */
  35. public $date_add;
  36. /** @var string Object last modification date */
  37. public $date_upd;
  38. /**
  39. * @see ObjectModel::$definition
  40. */
  41. public static $definition = array(
  42. 'table' => 'order_history',
  43. 'primary' => 'id_order_history',
  44. 'fields' => array(
  45. 'id_order' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
  46. 'id_order_state' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
  47. 'id_employee' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
  48. 'date_add' => array('type' => self::TYPE_DATE, 'validate' => 'isDate'),
  49. ),
  50. );
  51. /**
  52. * @see ObjectModel::$webserviceParameters
  53. */
  54. protected $webserviceParameters = array(
  55. 'objectsNodeName' => 'order_histories',
  56. 'fields' => array(
  57. 'id_employee' => array('xlink_resource'=> 'employees'),
  58. 'id_order_state' => array('required' => true, 'xlink_resource'=> 'order_states'),
  59. 'id_order' => array('xlink_resource' => 'orders'),
  60. ),
  61. 'objectMethods' => array(
  62. 'add' => 'addWs',
  63. ),
  64. );
  65. /**
  66. * Sets the new state of the given order
  67. *
  68. * @param int $new_order_state
  69. * @param int/object $id_order
  70. * @param bool $use_existing_payment
  71. */
  72. public function changeIdOrderState($new_order_state, $id_order, $use_existing_payment = false)
  73. {
  74. if (!$new_order_state || !$id_order)
  75. return;
  76. if (!is_object($id_order) && is_numeric($id_order))
  77. $order = new Order((int)$id_order);
  78. elseif (is_object($id_order))
  79. $order = $id_order;
  80. else
  81. return;
  82. ShopUrl::cacheMainDomainForShop($order->id_shop);
  83. $new_os = new OrderState((int)$new_order_state, $order->id_lang);
  84. $old_os = $order->getCurrentOrderState();
  85. // executes hook
  86. if (in_array($new_os->id, array(Configuration::get('PS_OS_PAYMENT'), Configuration::get('PS_OS_WS_PAYMENT'))))
  87. Hook::exec('actionPaymentConfirmation', array('id_order' => (int)$order->id), null, false, true, false, $order->id_shop);
  88. // executes hook
  89. Hook::exec('actionOrderStatusUpdate', array('newOrderStatus' => $new_os, 'id_order' => (int)$order->id), null, false, true, false, $order->id_shop);
  90. if (Validate::isLoadedObject($order) && ($new_os instanceof OrderState))
  91. {
  92. // An email is sent the first time a virtual item is validated
  93. $virtual_products = $order->getVirtualProducts();
  94. if ($virtual_products && (!$old_os || !$old_os->logable) && $new_os && $new_os->logable)
  95. {
  96. $context = Context::getContext();
  97. $assign = array();
  98. foreach ($virtual_products as $key => $virtual_product)
  99. {
  100. $id_product_download = ProductDownload::getIdFromIdProduct($virtual_product['product_id']);
  101. $product_download = new ProductDownload($id_product_download);
  102. // If this virtual item has an associated file, we'll provide the link to download the file in the email
  103. if ($product_download->display_filename != '')
  104. {
  105. $assign[$key]['name'] = $product_download->display_filename;
  106. $dl_link = $product_download->getTextLink(false, $virtual_product['download_hash'])
  107. .'&id_order='.(int)$order->id
  108. .'&secure_key='.$order->secure_key;
  109. $assign[$key]['link'] = $dl_link;
  110. if (isset($virtual_product['download_deadline']) && $virtual_product['download_deadline'] != '0000-00-00 00:00:00')
  111. $assign[$key]['deadline'] = Tools::displayDate($virtual_product['download_deadline']);
  112. if ($product_download->nb_downloadable != 0)
  113. $assign[$key]['downloadable'] = (int)$product_download->nb_downloadable;
  114. }
  115. }
  116. $customer = new Customer((int)$order->id_customer);
  117. $links = '<ul>';
  118. foreach ($assign as $product)
  119. {
  120. $links .= '<li>';
  121. $links .= '<a href="'.$product['link'].'">'.Tools::htmlentitiesUTF8($product['name']).'</a>';
  122. if (isset($product['deadline']))
  123. $links .= '&nbsp;'.Tools::htmlentitiesUTF8(Tools::displayError('expires on', false)).'&nbsp;'.$product['deadline'];
  124. if (isset($product['downloadable']))
  125. $links .= '&nbsp;'.Tools::htmlentitiesUTF8(sprintf(Tools::displayError('downloadable %d time(s)', false), (int)$product['downloadable']));
  126. $links .= '</li>';
  127. }
  128. $links .= '</ul>';
  129. $data = array(
  130. '{lastname}' => $customer->lastname,
  131. '{firstname}' => $customer->firstname,
  132. '{id_order}' => (int)$order->id,
  133. '{order_name}' => $order->getUniqReference(),
  134. '{nbProducts}' => count($virtual_products),
  135. '{virtualProducts}' => $links
  136. );
  137. // If there is at least one downloadable file
  138. if (!empty($assign))
  139. Mail::Send((int)$order->id_lang, 'download_product', Mail::l('The virtual product that you bought is available for download', $order->id_lang), $data, $customer->email, $customer->firstname.' '.$customer->lastname,
  140. null, null, null, null, _PS_MAIL_DIR_, false, (int)$order->id_shop);
  141. }
  142. // @since 1.5.0 : gets the stock manager
  143. $manager = null;
  144. if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT'))
  145. $manager = StockManagerFactory::getManager();
  146. $errorOrCanceledStatuses = array(Configuration::get('PS_OS_ERROR'), Configuration::get('PS_OS_CANCELED'));
  147. // foreach products of the order
  148. if (Validate::isLoadedObject($old_os))
  149. foreach ($order->getProductsDetail() as $product)
  150. {
  151. // if becoming logable => adds sale
  152. if ($new_os->logable && !$old_os->logable)
  153. {
  154. ProductSale::addProductSale($product['product_id'], $product['product_quantity']);
  155. // @since 1.5.0 - Stock Management
  156. if (!Pack::isPack($product['product_id']) &&
  157. in_array($old_os->id, $errorOrCanceledStatuses) &&
  158. !StockAvailable::dependsOnStock($product['id_product'], (int)$order->id_shop))
  159. StockAvailable::updateQuantity($product['product_id'], $product['product_attribute_id'], -(int)$product['product_quantity'], $order->id_shop);
  160. }
  161. // if becoming unlogable => removes sale
  162. elseif (!$new_os->logable && $old_os->logable)
  163. {
  164. ProductSale::removeProductSale($product['product_id'], $product['product_quantity']);
  165. // @since 1.5.0 - Stock Management
  166. if (!Pack::isPack($product['product_id']) &&
  167. in_array($new_os->id, $errorOrCanceledStatuses) &&
  168. !StockAvailable::dependsOnStock($product['id_product']))
  169. StockAvailable::updateQuantity($product['product_id'], $product['product_attribute_id'], (int)$product['product_quantity'], $order->id_shop);
  170. }
  171. // if waiting for payment => payment error/canceled
  172. elseif (!$new_os->logable && !$old_os->logable &&
  173. in_array($new_os->id, $errorOrCanceledStatuses) &&
  174. !in_array($old_os->id, $errorOrCanceledStatuses) &&
  175. !StockAvailable::dependsOnStock($product['id_product']))
  176. StockAvailable::updateQuantity($product['product_id'], $product['product_attribute_id'], (int)$product['product_quantity'], $order->id_shop);
  177. if ((int)$this->id_employee)
  178. $this->id_employee = Validate::isLoadedObject(new Employee((int)$this->id_employee)) ? $this->id_employee : 0;
  179. // @since 1.5.0 : if the order is being shipped and this products uses the advanced stock management :
  180. // decrements the physical stock using $id_warehouse
  181. if ($new_os->shipped == 1 && $old_os->shipped == 0 &&
  182. Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT') &&
  183. Warehouse::exists($product['id_warehouse']) &&
  184. $manager != null &&
  185. (int)$product['advanced_stock_management'] == 1)
  186. {
  187. // gets the warehouse
  188. $warehouse = new Warehouse($product['id_warehouse']);
  189. // decrements the stock (if it's a pack, the StockManager does what is needed)
  190. $manager->removeProduct(
  191. $product['product_id'],
  192. $product['product_attribute_id'],
  193. $warehouse,
  194. $product['product_quantity'],
  195. Configuration::get('PS_STOCK_CUSTOMER_ORDER_REASON'),
  196. true,
  197. (int)$order->id,
  198. 0,
  199. (int)$this->id_employee
  200. );
  201. }
  202. // @since.1.5.0 : if the order was shipped, and is not anymore, we need to restock products
  203. elseif ($new_os->shipped == 0 && $old_os->shipped == 1 &&
  204. Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT') &&
  205. Warehouse::exists($product['id_warehouse']) &&
  206. $manager != null &&
  207. (int)$product['advanced_stock_management'] == 1)
  208. {
  209. // if the product is a pack, we restock every products in the pack using the last negative stock mvts
  210. if (Pack::isPack($product['product_id']))
  211. {
  212. $pack_products = Pack::getItems($product['product_id'], Configuration::get('PS_LANG_DEFAULT', null, null, $order->id_shop));
  213. foreach ($pack_products as $pack_product)
  214. {
  215. if ($pack_product->advanced_stock_management == 1)
  216. {
  217. $mvts = StockMvt::getNegativeStockMvts($order->id, $pack_product->id, 0, $pack_product->pack_quantity * $product['product_quantity']);
  218. foreach ($mvts as $mvt)
  219. {
  220. $manager->addProduct(
  221. $pack_product->id,
  222. 0,
  223. new Warehouse($mvt['id_warehouse']),
  224. $mvt['physical_quantity'],
  225. null,
  226. $mvt['price_te'],
  227. true,
  228. (int)$this->id_employee
  229. );
  230. }
  231. if (!StockAvailable::dependsOnStock($product['id_product']))
  232. StockAvailable::updateQuantity($pack_product->id, 0, (int)$pack_product->pack_quantity * $product['product_quantity'], $order->id_shop);
  233. }
  234. }
  235. }
  236. // else, it's not a pack, re-stock using the last negative stock mvts
  237. else
  238. {
  239. $mvts = StockMvt::getNegativeStockMvts($order->id, $product['product_id'], $product['product_attribute_id'], $product['product_quantity']);
  240. foreach ($mvts as $mvt)
  241. {
  242. $manager->addProduct(
  243. $product['product_id'],
  244. $product['product_attribute_id'],
  245. new Warehouse($mvt['id_warehouse']),
  246. $mvt['physical_quantity'],
  247. null,
  248. $mvt['price_te'],
  249. true
  250. );
  251. }
  252. }
  253. }
  254. }
  255. }
  256. $this->id_order_state = (int)$new_order_state;
  257. // changes invoice number of order ?
  258. if (!Validate::isLoadedObject($new_os) || !Validate::isLoadedObject($order))
  259. die(Tools::displayError('Invalid new order status'));
  260. // the order is valid if and only if the invoice is available and the order is not cancelled
  261. $order->current_state = $this->id_order_state;
  262. $order->valid = $new_os->logable;
  263. $order->update();
  264. if ($new_os->invoice && !$order->invoice_number)
  265. $order->setInvoice($use_existing_payment);
  266. elseif ($new_os->delivery && !$order->delivery_number)
  267. $order->setDeliverySlip();
  268. // set orders as paid
  269. if ($new_os->paid == 1)
  270. {
  271. $invoices = $order->getInvoicesCollection();
  272. if ($order->total_paid != 0)
  273. $payment_method = Module::getInstanceByName($order->module);
  274. foreach ($invoices as $invoice)
  275. {
  276. $rest_paid = $invoice->getRestPaid();
  277. if ($rest_paid > 0)
  278. {
  279. $payment = new OrderPayment();
  280. $payment->order_reference = Tools::substr($order->reference, 0, 9);
  281. $payment->id_currency = $order->id_currency;
  282. $payment->amount = $rest_paid;
  283. if ($order->total_paid != 0)
  284. $payment->payment_method = $payment_method->displayName;
  285. else
  286. $payment->payment_method = null;
  287. // Update total_paid_real value for backward compatibility reasons
  288. if ($payment->id_currency == $order->id_currency)
  289. $order->total_paid_real += $payment->amount;
  290. else
  291. $order->total_paid_real += Tools::ps_round(Tools::convertPrice($payment->amount, $payment->id_currency, false), 2);
  292. $order->save();
  293. $payment->conversion_rate = 1;
  294. $payment->save();
  295. Db::getInstance()->execute('
  296. INSERT INTO `'._DB_PREFIX_.'order_invoice_payment` (`id_order_invoice`, `id_order_payment`, `id_order`)
  297. VALUES('.(int)$invoice->id.', '.(int)$payment->id.', '.(int)$order->id.')');
  298. }
  299. }
  300. }
  301. // updates delivery date even if it was already set by another state change
  302. if ($new_os->delivery)
  303. $order->setDelivery();
  304. // executes hook
  305. Hook::exec('actionOrderStatusPostUpdate', array('newOrderStatus' => $new_os,'id_order' => (int)$order->id,), null, false, true, false, $order->id_shop);
  306. ShopUrl::resetMainDomainCache();
  307. }
  308. /**
  309. * Returns the last order status
  310. * @param int $id_order
  311. * @return OrderState|bool
  312. * @deprecated 1.5.0.4
  313. * @see Order->current_state
  314. */
  315. public static function getLastOrderState($id_order)
  316. {
  317. Tools::displayAsDeprecated();
  318. $id_order_state = Db::getInstance()->getValue('
  319. SELECT `id_order_state`
  320. FROM `'._DB_PREFIX_.'order_history`
  321. WHERE `id_order` = '.(int)$id_order.'
  322. ORDER BY `date_add` DESC, `id_order_history` DESC');
  323. // returns false if there is no state
  324. if (!$id_order_state)
  325. return false;
  326. // else, returns an OrderState object
  327. return new OrderState($id_order_state, Configuration::get('PS_LANG_DEFAULT'));
  328. }
  329. /**
  330. * @param bool $autodate Optional
  331. * @param array $template_vars Optional
  332. * @param Context $context Optional
  333. * @return bool
  334. */
  335. public function addWithemail($autodate = true, $template_vars = false, Context $context = null)
  336. {
  337. if (!$context)
  338. $context = Context::getContext();
  339. $order = new Order($this->id_order);
  340. if (!$this->add($autodate))
  341. return false;
  342. $result = Db::getInstance()->getRow('
  343. SELECT osl.`template`, c.`lastname`, c.`firstname`, osl.`name` AS osname, c.`email`, os.`module_name`, os.`id_order_state`, os.`pdf_invoice`, os.`pdf_delivery`
  344. FROM `'._DB_PREFIX_.'order_history` oh
  345. LEFT JOIN `'._DB_PREFIX_.'orders` o ON oh.`id_order` = o.`id_order`
  346. LEFT JOIN `'._DB_PREFIX_.'customer` c ON o.`id_customer` = c.`id_customer`
  347. LEFT JOIN `'._DB_PREFIX_.'order_state` os ON oh.`id_order_state` = os.`id_order_state`
  348. LEFT JOIN `'._DB_PREFIX_.'order_state_lang` osl ON (os.`id_order_state` = osl.`id_order_state` AND osl.`id_lang` = o.`id_lang`)
  349. WHERE oh.`id_order_history` = '.(int)$this->id.' AND os.`send_email` = 1');
  350. if (isset($result['template']) && Validate::isEmail($result['email']))
  351. {
  352. ShopUrl::cacheMainDomainForShop($order->id_shop);
  353. $topic = $result['osname'];
  354. $data = array(
  355. '{lastname}' => $result['lastname'],
  356. '{firstname}' => $result['firstname'],
  357. '{id_order}' => (int)$this->id_order,
  358. '{order_name}' => $order->getUniqReference()
  359. );
  360. if ($result['module_name'])
  361. {
  362. $module = Module::getInstanceByName($result['module_name']);
  363. if (Validate::isLoadedObject($module) && isset($module->extra_mail_vars) && is_array($module->extra_mail_vars))
  364. $data = array_merge($data, $module->extra_mail_vars);
  365. }
  366. if ($template_vars)
  367. $data = array_merge($data, $template_vars);
  368. $data['{total_paid}'] = Tools::displayPrice((float)$order->total_paid, new Currency((int)$order->id_currency), false);
  369. if (Validate::isLoadedObject($order))
  370. {
  371. // Attach invoice and / or delivery-slip if they exists and status is set to attach them
  372. if (($result['pdf_invoice'] || $result['pdf_delivery']))
  373. {
  374. $context = Context::getContext();
  375. $invoice = $order->getInvoicesCollection();
  376. $file_attachement = array();
  377. if ($result['pdf_invoice'] && (int)Configuration::get('PS_INVOICE') && $order->invoice_number)
  378. {
  379. $pdf = new PDF($invoice, PDF::TEMPLATE_INVOICE, $context->smarty);
  380. $file_attachement['invoice']['content'] = $pdf->render(false);
  381. $file_attachement['invoice']['name'] = Configuration::get('PS_INVOICE_PREFIX', (int)$order->id_lang, null, $order->id_shop).sprintf('%06d', $order->invoice_number).'.pdf';
  382. $file_attachement['invoice']['mime'] = 'application/pdf';
  383. }
  384. if ($result['pdf_delivery'] && $order->delivery_number)
  385. {
  386. $pdf = new PDF($invoice, PDF::TEMPLATE_DELIVERY_SLIP, $context->smarty);
  387. $file_attachement['delivery']['content'] = $pdf->render(false);
  388. $file_attachement['delivery']['name'] = Configuration::get('PS_DELIVERY_PREFIX', Context::getContext()->language->id, null, $this->order->id_shop).sprintf('%06d', $this->order->delivery_number).'.pdf';
  389. $file_attachement['delivery']['mime'] = 'application/pdf';
  390. }
  391. }
  392. else
  393. $file_attachement = null;
  394. Mail::Send((int)$order->id_lang, $result['template'], $topic, $data, $result['email'], $result['firstname'].' '.$result['lastname'],
  395. null, null, $file_attachement, null, _PS_MAIL_DIR_, false, (int)$order->id_shop);
  396. }
  397. ShopUrl::resetMainDomainCache();
  398. }
  399. return true;
  400. }
  401. public function add($autodate = true, $null_values = false)
  402. {
  403. if (!parent::add($autodate))
  404. return false;
  405. $order = new Order((int)$this->id_order);
  406. // Update id_order_state attribute in Order
  407. $order->current_state = $this->id_order_state;
  408. $order->update();
  409. Hook::exec('actionOrderHistoryAddAfter', array('order_history' => $this), null, false, true, false, $order->id_shop);
  410. return true;
  411. }
  412. /**
  413. * @return int
  414. */
  415. public function isValidated()
  416. {
  417. return Db::getInstance()->getValue('
  418. SELECT COUNT(oh.`id_order_history`) AS nb
  419. FROM `'._DB_PREFIX_.'order_state` os
  420. LEFT JOIN `'._DB_PREFIX_.'order_history` oh ON (os.`id_order_state` = oh.`id_order_state`)
  421. WHERE oh.`id_order` = '.(int)$this->id_order.'
  422. AND os.`logable` = 1');
  423. }
  424. /**
  425. * Add method for webservice create resource Order History
  426. * If sendemail=1 GET parameter is present sends email to customer otherwise does not
  427. * @return bool
  428. */
  429. public function addWs()
  430. {
  431. $sendemail = (bool)Tools::getValue('sendemail', false);
  432. $this->changeIdOrderState($this->id_order_state, $this->id_order);
  433. if ($sendemail)
  434. {
  435. //Mail::Send requires link object on context and is not set when getting here
  436. $context = Context::getContext();
  437. if ($context->link == null)
  438. {
  439. $protocol_link = (Tools::usingSecureMode() && Configuration::get('PS_SSL_ENABLED')) ? 'https://' : 'http://';
  440. $protocol_content = (Tools::usingSecureMode() && Configuration::get('PS_SSL_ENABLED')) ? 'https://' : 'http://';
  441. $context->link = new Link($protocol_link, $protocol_content);
  442. }
  443. return $this->addWithemail();
  444. }
  445. else
  446. return $this->add();
  447. }
  448. }