PageRenderTime 37ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/classes/order/OrderHistory.php

https://github.com/netplayer/PrestaShop
PHP | 486 lines | 339 code | 54 blank | 93 comment | 83 complexity | 5b570a565ece5b056c5555fb6f88b63d MD5 | raw file
Possible License(s): CC-BY-SA-3.0, LGPL-2.1, 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_order_state' => array('required' => true, 'xlink_resource'=> 'order_states'),
  58. 'id_order' => array('xlink_resource' => 'orders'),
  59. ),
  60. 'objectMethods' => array(
  61. 'add' => 'addWs',
  62. ),
  63. );
  64. /**
  65. * Sets the new state of the given order
  66. *
  67. * @param int $new_order_state
  68. * @param int/object $id_order
  69. * @param bool $use_existing_payment
  70. */
  71. public function changeIdOrderState($new_order_state, $id_order, $use_existing_payment = false)
  72. {
  73. if (!$new_order_state || !$id_order)
  74. return;
  75. if (!is_object($id_order) && is_numeric($id_order))
  76. $order = new Order((int)$id_order);
  77. elseif (is_object($id_order))
  78. $order = $id_order;
  79. else
  80. return;
  81. ShopUrl::cacheMainDomainForShop($order->id_shop);
  82. $new_os = new OrderState((int)$new_order_state, $order->id_lang);
  83. $old_os = $order->getCurrentOrderState();
  84. $is_validated = $this->isValidated();
  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('Virtual product to 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. // @since 1.5.0 : if the order is being shipped and this products uses the advanced stock management :
  178. // decrements the physical stock using $id_warehouse
  179. if ($new_os->shipped == 1 && $old_os->shipped == 0 &&
  180. Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT') &&
  181. Warehouse::exists($product['id_warehouse']) &&
  182. $manager != null &&
  183. ((int)$product['advanced_stock_management'] == 1 || Pack::usesAdvancedStockManagement($product['product_id'])))
  184. {
  185. // gets the warehouse
  186. $warehouse = new Warehouse($product['id_warehouse']);
  187. // decrements the stock (if it's a pack, the StockManager does what is needed)
  188. $manager->removeProduct(
  189. $product['product_id'],
  190. $product['product_attribute_id'],
  191. $warehouse,
  192. $product['product_quantity'],
  193. Configuration::get('PS_STOCK_CUSTOMER_ORDER_REASON'),
  194. true,
  195. (int)$order->id
  196. );
  197. }
  198. // @since.1.5.0 : if the order was shipped, and is not anymore, we need to restock products
  199. elseif ($new_os->shipped == 0 && $old_os->shipped == 1 &&
  200. Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT') &&
  201. Warehouse::exists($product['id_warehouse']) &&
  202. $manager != null &&
  203. ((int)$product['advanced_stock_management'] == 1 || Pack::usesAdvancedStockManagement($product['product_id'])))
  204. {
  205. // if the product is a pack, we restock every products in the pack using the last negative stock mvts
  206. if (Pack::isPack($product['product_id']))
  207. {
  208. $pack_products = Pack::getItems($product['product_id'], Configuration::get('PS_LANG_DEFAULT', null, null, $order->id_shop));
  209. foreach ($pack_products as $pack_product)
  210. {
  211. if ($pack_product->advanced_stock_management == 1)
  212. {
  213. $mvts = StockMvt::getNegativeStockMvts($order->id, $pack_product->id, 0, $pack_product->pack_quantity * $product['product_quantity']);
  214. foreach ($mvts as $mvt)
  215. {
  216. $manager->addProduct(
  217. $pack_product->id,
  218. 0,
  219. new Warehouse($mvt['id_warehouse']),
  220. $mvt['physical_quantity'],
  221. null,
  222. $mvt['price_te'],
  223. true
  224. );
  225. }
  226. if (!StockAvailable::dependsOnStock($product['id_product']))
  227. StockAvailable::updateQuantity($pack_product->id, 0, (int)$pack_product->pack_quantity * $product['product_quantity'], $order->id_shop);
  228. }
  229. }
  230. }
  231. // else, it's not a pack, re-stock using the last negative stock mvts
  232. else
  233. {
  234. $mvts = StockMvt::getNegativeStockMvts($order->id, $product['product_id'], $product['product_attribute_id'], $product['product_quantity']);
  235. foreach ($mvts as $mvt)
  236. {
  237. $manager->addProduct(
  238. $product['product_id'],
  239. $product['product_attribute_id'],
  240. new Warehouse($mvt['id_warehouse']),
  241. $mvt['physical_quantity'],
  242. null,
  243. $mvt['price_te'],
  244. true
  245. );
  246. }
  247. }
  248. }
  249. }
  250. }
  251. $this->id_order_state = (int)$new_order_state;
  252. // changes invoice number of order ?
  253. if (!Validate::isLoadedObject($new_os) || !Validate::isLoadedObject($order))
  254. die(Tools::displayError('Invalid new order status'));
  255. // the order is valid if and only if the invoice is available and the order is not cancelled
  256. $order->current_state = $this->id_order_state;
  257. $order->valid = $new_os->logable;
  258. $order->update();
  259. if ($new_os->invoice && !$order->invoice_number)
  260. $order->setInvoice($use_existing_payment);
  261. elseif ($new_os->delivery && !$order->delivery_number)
  262. $order->setDeliverySlip();
  263. // set orders as paid
  264. if ($new_os->paid == 1)
  265. {
  266. $invoices = $order->getInvoicesCollection();
  267. if ($order->total_paid != 0)
  268. $payment_method = Module::getInstanceByName($order->module);
  269. foreach ($invoices as $invoice)
  270. {
  271. $rest_paid = $invoice->getRestPaid();
  272. if ($rest_paid > 0)
  273. {
  274. $payment = new OrderPayment();
  275. $payment->order_reference = $order->reference;
  276. $payment->id_currency = $order->id_currency;
  277. $payment->amount = $rest_paid;
  278. if ($order->total_paid != 0)
  279. $payment->payment_method = $payment_method->displayName;
  280. else
  281. $payment->payment_method = null;
  282. // Update total_paid_real value for backward compatibility reasons
  283. if ($payment->id_currency == $order->id_currency)
  284. $order->total_paid_real += $payment->amount;
  285. else
  286. $order->total_paid_real += Tools::ps_round(Tools::convertPrice($payment->amount, $payment->id_currency, false), 2);
  287. $order->save();
  288. $payment->conversion_rate = 1;
  289. $payment->save();
  290. Db::getInstance()->execute('
  291. INSERT INTO `'._DB_PREFIX_.'order_invoice_payment` (`id_order_invoice`, `id_order_payment`, `id_order`)
  292. VALUES('.(int)$invoice->id.', '.(int)$payment->id.', '.(int)$order->id.')');
  293. }
  294. }
  295. }
  296. // updates delivery date even if it was already set by another state change
  297. if ($new_os->delivery)
  298. $order->setDelivery();
  299. // executes hook
  300. Hook::exec('actionOrderStatusPostUpdate', array('newOrderStatus' => $new_os,'id_order' => (int)$order->id,), null, false, true, false, $order->id_shop);
  301. ShopUrl::resetMainDomainCache();
  302. }
  303. /**
  304. * Returns the last order status
  305. * @param int $id_order
  306. * @return OrderState|bool
  307. * @deprecated 1.5.0.4
  308. * @see Order->current_state
  309. */
  310. public static function getLastOrderState($id_order)
  311. {
  312. Tools::displayAsDeprecated();
  313. $id_order_state = Db::getInstance()->getValue('
  314. SELECT `id_order_state`
  315. FROM `'._DB_PREFIX_.'order_history`
  316. WHERE `id_order` = '.(int)$id_order.'
  317. ORDER BY `date_add` DESC, `id_order_history` DESC');
  318. // returns false if there is no state
  319. if (!$id_order_state)
  320. return false;
  321. // else, returns an OrderState object
  322. return new OrderState($id_order_state, Configuration::get('PS_LANG_DEFAULT'));
  323. }
  324. /**
  325. * @param bool $autodate Optional
  326. * @param array $template_vars Optional
  327. * @param Context $context Optional
  328. * @return bool
  329. */
  330. public function addWithemail($autodate = true, $template_vars = false, Context $context = null)
  331. {
  332. if (!$context)
  333. $context = Context::getContext();
  334. $order = new Order($this->id_order);
  335. if (!$this->add($autodate))
  336. return false;
  337. $result = Db::getInstance()->getRow('
  338. SELECT osl.`template`, c.`lastname`, c.`firstname`, osl.`name` AS osname, c.`email`, os.`module_name`, os.`id_order_state`
  339. FROM `'._DB_PREFIX_.'order_history` oh
  340. LEFT JOIN `'._DB_PREFIX_.'orders` o ON oh.`id_order` = o.`id_order`
  341. LEFT JOIN `'._DB_PREFIX_.'customer` c ON o.`id_customer` = c.`id_customer`
  342. LEFT JOIN `'._DB_PREFIX_.'order_state` os ON oh.`id_order_state` = os.`id_order_state`
  343. LEFT JOIN `'._DB_PREFIX_.'order_state_lang` osl ON (os.`id_order_state` = osl.`id_order_state` AND osl.`id_lang` = o.`id_lang`)
  344. WHERE oh.`id_order_history` = '.(int)$this->id.' AND os.`send_email` = 1');
  345. if (isset($result['template']) && Validate::isEmail($result['email']))
  346. {
  347. ShopUrl::cacheMainDomainForShop($order->id_shop);
  348. $topic = $result['osname'];
  349. $data = array(
  350. '{lastname}' => $result['lastname'],
  351. '{firstname}' => $result['firstname'],
  352. '{id_order}' => (int)$this->id_order,
  353. '{order_name}' => $order->getUniqReference()
  354. );
  355. if ($template_vars)
  356. $data = array_merge($data, $template_vars);
  357. if ($result['module_name'])
  358. {
  359. $module = Module::getInstanceByName($result['module_name']);
  360. if (Validate::isLoadedObject($module) && isset($module->extra_mail_vars) && is_array($module->extra_mail_vars))
  361. $data = array_merge($data, $module->extra_mail_vars);
  362. }
  363. $data['{total_paid}'] = Tools::displayPrice((float)$order->total_paid, new Currency((int)$order->id_currency), false);
  364. $data['{order_name}'] = $order->getUniqReference();
  365. if (Validate::isLoadedObject($order))
  366. {
  367. // Join PDF invoice if order status is "payment accepted"
  368. if ((int)$result['id_order_state'] === 2 && (int)Configuration::get('PS_INVOICE') && $order->invoice_number)
  369. {
  370. $context = Context::getContext();
  371. $pdf = new PDF($order->getInvoicesCollection(), PDF::TEMPLATE_INVOICE, $context->smarty);
  372. $file_attachement['content'] = $pdf->render(false);
  373. $file_attachement['name'] = Configuration::get('PS_INVOICE_PREFIX', (int)$order->id_lang, null, $order->id_shop).sprintf('%06d', $order->invoice_number).'.pdf';
  374. $file_attachement['mime'] = 'application/pdf';
  375. }
  376. else
  377. $file_attachement = null;
  378. Mail::Send((int)$order->id_lang, $result['template'], $topic, $data, $result['email'], $result['firstname'].' '.$result['lastname'],
  379. null, null, $file_attachement, null, _PS_MAIL_DIR_, false, (int)$order->id_shop);
  380. }
  381. ShopUrl::resetMainDomainCache();
  382. }
  383. return true;
  384. }
  385. public function add($autodate = true, $null_values = false)
  386. {
  387. if (!parent::add($autodate))
  388. return false;
  389. $order = new Order((int)$this->id_order);
  390. // Update id_order_state attribute in Order
  391. $order->current_state = $this->id_order_state;
  392. $order->update();
  393. Hook::exec('actionOrderHistoryAddAfter', array('order_history' => $this), null, false, true, false, $order->id_shop);
  394. return true;
  395. }
  396. /**
  397. * @return int
  398. */
  399. public function isValidated()
  400. {
  401. return Db::getInstance()->getValue('
  402. SELECT COUNT(oh.`id_order_history`) AS nb
  403. FROM `'._DB_PREFIX_.'order_state` os
  404. LEFT JOIN `'._DB_PREFIX_.'order_history` oh ON (os.`id_order_state` = oh.`id_order_state`)
  405. WHERE oh.`id_order` = '.(int)$this->id_order.'
  406. AND os.`logable` = 1');
  407. }
  408. /**
  409. * Add method for webservice create resource Order History
  410. * If sendemail=1 GET parameter is present sends email to customer otherwise does not
  411. * @return bool
  412. */
  413. public function addWs()
  414. {
  415. $sendemail = (bool)Tools::getValue('sendemail', false);
  416. $this->changeIdOrderState($this->id_order_state, $this->id_order);
  417. if ($sendemail)
  418. {
  419. //Mail::Send requires link object on context and is not set when getting here
  420. $context = Context::getContext();
  421. if ($context->link == null)
  422. {
  423. $protocol_link = (Tools::usingSecureMode() && Configuration::get('PS_SSL_ENABLED')) ? 'https://' : 'http://';
  424. $protocol_content = (Tools::usingSecureMode() && Configuration::get('PS_SSL_ENABLED')) ? 'https://' : 'http://';
  425. $context->link = new Link($protocol_link, $protocol_content);
  426. }
  427. return $this->addWithemail();
  428. }
  429. else
  430. return $this->add();
  431. }
  432. }