*/ class Mage_Sales_Model_Order_Creditmemo extends Mage_Sales_Model_Abstract { const STATE_OPEN = 1; const STATE_REFUNDED = 2; const STATE_CANCELED = 3; const XML_PATH_EMAIL_TEMPLATE = 'sales_email/creditmemo/template'; const XML_PATH_EMAIL_GUEST_TEMPLATE = 'sales_email/creditmemo/guest_template'; const XML_PATH_EMAIL_IDENTITY = 'sales_email/creditmemo/identity'; const XML_PATH_EMAIL_COPY_TO = 'sales_email/creditmemo/copy_to'; const XML_PATH_EMAIL_COPY_METHOD = 'sales_email/creditmemo/copy_method'; const XML_PATH_EMAIL_ENABLED = 'sales_email/creditmemo/enabled'; const XML_PATH_UPDATE_EMAIL_TEMPLATE = 'sales_email/creditmemo_comment/template'; const XML_PATH_UPDATE_EMAIL_GUEST_TEMPLATE = 'sales_email/creditmemo_comment/guest_template'; const XML_PATH_UPDATE_EMAIL_IDENTITY = 'sales_email/creditmemo_comment/identity'; const XML_PATH_UPDATE_EMAIL_COPY_TO = 'sales_email/creditmemo_comment/copy_to'; const XML_PATH_UPDATE_EMAIL_COPY_METHOD = 'sales_email/creditmemo_comment/copy_method'; const XML_PATH_UPDATE_EMAIL_ENABLED = 'sales_email/creditmemo_comment/enabled'; const REPORT_DATE_TYPE_ORDER_CREATED = 'order_created'; const REPORT_DATE_TYPE_REFUND_CREATED = 'refund_created'; /* * Identifier for order history item */ const HISTORY_ENTITY_NAME = 'creditmemo'; protected static $_states; protected $_items; protected $_order; protected $_comments; /** * Calculator instances for delta rounding of prices * * @var array */ protected $_calculators = array(); protected $_eventPrefix = 'sales_order_creditmemo'; protected $_eventObject = 'creditmemo'; /** * Initialize creditmemo resource model */ protected function _construct() { $this->_init('sales/order_creditmemo'); } /** * Init mapping array of short fields to its full names * * @return Mage_Sales_Model_Order_Creditmemo */ protected function _initOldFieldsMap() { $this->_oldFieldsMap = Mage::helper('sales')->getOldFieldMap('order_creditmemo'); return $this; } /** * Retrieve Creditmemo configuration model * * @return Mage_Sales_Model_Order_Creditmemo_Config */ public function getConfig() { return Mage::getSingleton('sales/order_creditmemo_config'); } /** * Retrieve creditmemo store instance * * @return Mage_Core_Model_Store */ public function getStore() { return $this->getOrder()->getStore(); } /** * Declare order for creditmemo * * @param Mage_Sales_Model_Order $order * @return Mage_Sales_Model_Order_Creditmemo */ public function setOrder(Mage_Sales_Model_Order $order) { $this->_order = $order; $this->setOrderId($order->getId()) ->setStoreId($order->getStoreId()); return $this; } /** * Retrieve the order the creditmemo for created for * * @return Mage_Sales_Model_Order */ public function getOrder() { if (!$this->_order instanceof Mage_Sales_Model_Order) { $this->_order = Mage::getModel('sales/order')->load($this->getOrderId()); } return $this->_order->setHistoryEntityName(self::HISTORY_ENTITY_NAME); } /** * Retrieve billing address * * @return Mage_Sales_Model_Order_Address */ public function getBillingAddress() { return $this->getOrder()->getBillingAddress(); } /** * Retrieve shipping address * * @return Mage_Sales_Model_Order_Address */ public function getShippingAddress() { return $this->getOrder()->getShippingAddress(); } public function getItemsCollection() { if (empty($this->_items)) { $this->_items = Mage::getResourceModel('sales/order_creditmemo_item_collection') ->setCreditmemoFilter($this->getId()); if ($this->getId()) { foreach ($this->_items as $item) { $item->setCreditmemo($this); } } } return $this->_items; } public function getAllItems() { $items = array(); foreach ($this->getItemsCollection() as $item) { if (!$item->isDeleted()) { $items[] = $item; } } return $items; } public function getItemById($itemId) { foreach ($this->getItemsCollection() as $item) { if ($item->getId()==$itemId) { return $item; } } return false; } /** * Returns credit memo item by its order id * * @param $orderId * @return Mage_Sales_Model_Order_Creditmemo_Item|bool */ public function getItemByOrderId($orderId) { foreach ($this->getItemsCollection() as $item) { if ($item->getOrderItemId() == $orderId) { return $item; } } return false; } public function addItem(Mage_Sales_Model_Order_Creditmemo_Item $item) { $item->setCreditmemo($this) ->setParentId($this->getId()) ->setStoreId($this->getStoreId()); if (!$item->getId()) { $this->getItemsCollection()->addItem($item); } return $this; } /** * Creditmemo totals collecting * * @return Mage_Sales_Model_Order_Creditmemo */ public function collectTotals() { foreach ($this->getConfig()->getTotalModels() as $model) { $model->collect($this); } return $this; } /** * Round price considering delta * * @param float $price * @param string $type * @param bool $negative Indicates if we perform addition (true) or subtraction (false) of rounded value * @return float */ public function roundPrice($price, $type = 'regular', $negative = false) { if ($price) { if (!isset($this->_calculators[$type])) { $this->_calculators[$type] = Mage::getModel('core/calculator', $this->getStore()); } $price = $this->_calculators[$type]->deltaRound($price, $negative); } return $price; } public function canRefund() { if ($this->getState() != self::STATE_CANCELED && $this->getState() != self::STATE_REFUNDED && $this->getOrder()->getPayment()->canRefund() ) { return true; } return false; } /** * Check creditmemo cancel action availability * * @return bool */ public function canCancel() { return $this->getState() == self::STATE_OPEN; } /** * Check invice void action availability * * @return bool */ public function canVoid() { $canVoid = false; return false; if ($this->getState() == self::STATE_REFUNDED) { $canVoid = $this->getCanVoidFlag(); /** * If we not retrieve negative answer from payment yet */ if (is_null($canVoid)) { $canVoid = $this->getOrder()->getPayment()->canVoid($this); if ($canVoid === false) { $this->setCanVoidFlag(false); $this->_saveBeforeDestruct = true; } } else { $canVoid = (bool) $canVoid; } } return $canVoid; } public function refund() { $this->setState(self::STATE_REFUNDED); $orderRefund = Mage::app()->getStore()->roundPrice( $this->getOrder()->getTotalRefunded()+$this->getGrandTotal() ); $baseOrderRefund = Mage::app()->getStore()->roundPrice( $this->getOrder()->getBaseTotalRefunded()+$this->getBaseGrandTotal() ); if ($baseOrderRefund > Mage::app()->getStore()->roundPrice($this->getOrder()->getBaseTotalPaid())) { $baseAvailableRefund = $this->getOrder()->getBaseTotalPaid()- $this->getOrder()->getBaseTotalRefunded(); Mage::throwException( Mage::helper('sales')->__('Maximum amount available to refund is %s', $this->getOrder()->formatBasePrice($baseAvailableRefund)) ); } $order = $this->getOrder(); $order->setBaseTotalRefunded($baseOrderRefund); $order->setTotalRefunded($orderRefund); $order->setBaseSubtotalRefunded($order->getBaseSubtotalRefunded()+$this->getBaseSubtotal()); $order->setSubtotalRefunded($order->getSubtotalRefunded()+$this->getSubtotal()); $order->setBaseTaxRefunded($order->getBaseTaxRefunded()+$this->getBaseTaxAmount()); $order->setTaxRefunded($order->getTaxRefunded()+$this->getTaxAmount()); $order->setBaseHiddenTaxRefunded($order->getBaseHiddenTaxRefunded()+$this->getBaseHiddenTaxAmount()); $order->setHiddenTaxRefunded($order->getHiddenTaxRefunded()+$this->getHiddenTaxAmount()); $order->setBaseShippingRefunded($order->getBaseShippingRefunded()+$this->getBaseShippingAmount()); $order->setShippingRefunded($order->getShippingRefunded()+$this->getShippingAmount()); $order->setBaseShippingTaxRefunded($order->getBaseShippingTaxRefunded()+$this->getBaseShippingTaxAmount()); $order->setShippingTaxRefunded($order->getShippingTaxRefunded()+$this->getShippingTaxAmount()); $order->setAdjustmentPositive($order->getAdjustmentPositive()+$this->getAdjustmentPositive()); $order->setBaseAdjustmentPositive($order->getBaseAdjustmentPositive()+$this->getBaseAdjustmentPositive()); $order->setAdjustmentNegative($order->getAdjustmentNegative()+$this->getAdjustmentNegative()); $order->setBaseAdjustmentNegative($order->getBaseAdjustmentNegative()+$this->getBaseAdjustmentNegative()); $order->setDiscountRefunded($order->getDiscountRefunded()+$this->getDiscountAmount()); $order->setBaseDiscountRefunded($order->getBaseDiscountRefunded()+$this->getBaseDiscountAmount()); if ($this->getInvoice()) { $this->getInvoice()->setIsUsedForRefund(true); $this->getInvoice()->setBaseTotalRefunded( $this->getInvoice()->getBaseTotalRefunded() + $this->getBaseGrandTotal() ); $this->setInvoiceId($this->getInvoice()->getId()); } if (!$this->getPaymentRefundDisallowed()) { $order->getPayment()->refund($this); } Mage::dispatchEvent('sales_order_creditmemo_refund', array($this->_eventObject=>$this)); return $this; } /** * Cancel Creditmemo action * * @return Mage_Sales_Model_Order_Creditmemo */ public function cancel() { $this->setState(self::STATE_CANCELED); foreach ($this->getAllItems() as $item) { $item->cancel(); } $this->getOrder()->getPayment()->cancelCreditmemo($this); if ($this->getTransactionId()) { $this->getOrder()->setTotalOnlineRefunded( $this->getOrder()->getTotalOnlineRefunded()-$this->getGrandTotal() ); $this->getOrder()->setBaseTotalOnlineRefunded( $this->getOrder()->getBaseTotalOnlineRefunded()-$this->getBaseGrandTotal() ); } else { $this->getOrder()->setTotalOfflineRefunded( $this->getOrder()->getTotalOfflineRefunded()-$this->getGrandTotal() ); $this->getOrder()->setBaseTotalOfflineRefunded( $this->getOrder()->getBaseTotalOfflineRefunded()-$this->getBaseGrandTotal() ); } $this->getOrder()->setBaseSubtotalRefunded( $this->getOrder()->getBaseSubtotalRefunded()-$this->getBaseSubtotal() ); $this->getOrder()->setSubtotalRefunded($this->getOrder()->getSubtotalRefunded()-$this->getSubtotal()); $this->getOrder()->setBaseTaxRefunded($this->getOrder()->getBaseTaxRefunded()-$this->getBaseTaxAmount()); $this->getOrder()->setTaxRefunded($this->getOrder()->getTaxRefunded()-$this->getTaxAmount()); $this->getOrder()->setBaseShippingRefunded( $this->getOrder()->getBaseShippingRefunded()-$this->getBaseShippingAmount() ); $this->getOrder()->setShippingRefunded($this->getOrder()->getShippingRefunded()-$this->getShippingAmount()); Mage::dispatchEvent('sales_order_creditmemo_cancel', array($this->_eventObject=>$this)); return $this; } /** * Register creditmemo * * Apply to order, order items etc. * * @return Mage_Sales_Model_Order_Creditmemo */ public function register() { if ($this->getId()) { Mage::throwException( Mage::helper('sales')->__('Cannot register an existing credit memo.') ); } foreach ($this->getAllItems() as $item) { if ($item->getQty()>0) { $item->register(); } else { $item->isDeleted(true); } } $this->setDoTransaction(true); if ($this->getOfflineRequested()) { $this->setDoTransaction(false); } $this->refund(); if ($this->getDoTransaction()) { $this->getOrder()->setTotalOnlineRefunded( $this->getOrder()->getTotalOnlineRefunded()+$this->getGrandTotal() ); $this->getOrder()->setBaseTotalOnlineRefunded( $this->getOrder()->getBaseTotalOnlineRefunded()+$this->getBaseGrandTotal() ); } else { $this->getOrder()->setTotalOfflineRefunded( $this->getOrder()->getTotalOfflineRefunded()+$this->getGrandTotal() ); $this->getOrder()->setBaseTotalOfflineRefunded( $this->getOrder()->getBaseTotalOfflineRefunded()+$this->getBaseGrandTotal() ); } $this->getOrder()->setBaseTotalInvoicedCost( $this->getOrder()->getBaseTotalInvoicedCost()-$this->getBaseCost() ); $state = $this->getState(); if (is_null($state)) { $this->setState(self::STATE_OPEN); } return $this; } /** * Retrieve Creditmemo states array * * @return array */ public static function getStates() { if (is_null(self::$_states)) { self::$_states = array( self::STATE_OPEN => Mage::helper('sales')->__('Pending'), self::STATE_REFUNDED => Mage::helper('sales')->__('Refunded'), self::STATE_CANCELED => Mage::helper('sales')->__('Canceled'), ); } return self::$_states; } /** * Retrieve Creditmemo state name by state identifier * * @param int $stateId * @return string */ public function getStateName($stateId = null) { if (is_null($stateId)) { $stateId = $this->getState(); } if (is_null(self::$_states)) { self::getStates(); } if (isset(self::$_states[$stateId])) { return self::$_states[$stateId]; } return Mage::helper('sales')->__('Unknown State'); } public function setShippingAmount($amount) { // base shipping amount calculated in total model // $amount = $this->getStore()->roundPrice($amount); // $this->setData('base_shipping_amount', $amount); // // $amount = $this->getStore()->roundPrice( // $amount*$this->getOrder()->getStoreToOrderRate() // ); $this->setData('shipping_amount', $amount); return $this; } public function setAdjustmentPositive($amount) { $amount = trim($amount); if (substr($amount, -1) == '%') { $amount = (float) substr($amount, 0, -1); $amount = $this->getOrder()->getGrandTotal() * $amount / 100; } $amount = $this->getStore()->roundPrice($amount); $this->setData('base_adjustment_positive', $amount); $amount = $this->getStore()->roundPrice( $amount*$this->getOrder()->getStoreToOrderRate() ); $this->setData('adjustment_positive', $amount); return $this; } public function setAdjustmentNegative($amount) { $amount = trim($amount); if (substr($amount, -1) == '%') { $amount = (float) substr($amount, 0, -1); $amount = $this->getOrder()->getGrandTotal() * $amount / 100; } $amount = $this->getStore()->roundPrice($amount); $this->setData('base_adjustment_negative', $amount); $amount = $this->getStore()->roundPrice( $amount*$this->getOrder()->getStoreToOrderRate() ); $this->setData('adjustment_negative', $amount); return $this; } /** * Adds comment to credit memo with additional possibility to send it to customer via email * and show it in customer account * * @param string $comment * @param bool $notify * @param bool $visibleOnFront * * @return Mage_Sales_Model_Order_Creditmemo */ public function addComment($comment, $notify=false, $visibleOnFront=false) { if (!($comment instanceof Mage_Sales_Model_Order_Creditmemo_Comment)) { $comment = Mage::getModel('sales/order_creditmemo_comment') ->setComment($comment) ->setIsCustomerNotified($notify) ->setIsVisibleOnFront($visibleOnFront); } $comment->setCreditmemo($this) ->setParentId($this->getId()) ->setStoreId($this->getStoreId()); if (!$comment->getId()) { $this->getCommentsCollection()->addItem($comment); } $this->_hasDataChanges = true; return $this; } public function getCommentsCollection($reload=false) { if (is_null($this->_comments) || $reload) { $this->_comments = Mage::getResourceModel('sales/order_creditmemo_comment_collection') ->setCreditmemoFilter($this->getId()) ->setCreatedAtOrder(); /** * When credit memo created with adding comment, * comments collection must be loaded before we added this comment. */ $this->_comments->load(); if ($this->getId()) { foreach ($this->_comments as $comment) { $comment->setCreditmemo($this); } } } return $this->_comments; } /** * Send email with creditmemo data * * @param boolean $notifyCustomer * @param string $comment * @return Mage_Sales_Model_Order_Creditmemo */ public function sendEmail($notifyCustomer = true, $comment = '') { $order = $this->getOrder(); $storeId = $order->getStore()->getId(); if (!Mage::helper('sales')->canSendNewCreditmemoEmail($storeId)) { return $this; } // Get the destination email addresses to send copies to $copyTo = $this->_getEmails(self::XML_PATH_EMAIL_COPY_TO); $copyMethod = Mage::getStoreConfig(self::XML_PATH_EMAIL_COPY_METHOD, $storeId); // Check if at least one recepient is found if (!$notifyCustomer && !$copyTo) { return $this; } // Start store emulation process $appEmulation = Mage::getSingleton('core/app_emulation'); $initialEnvironmentInfo = $appEmulation->startEnvironmentEmulation($storeId); try { // Retrieve specified view block from appropriate design package (depends on emulated store) $paymentBlock = Mage::helper('payment')->getInfoBlock($order->getPayment()) ->setIsSecureMode(true); $paymentBlock->getMethod()->setStore($storeId); $paymentBlockHtml = $paymentBlock->toHtml(); } catch (Exception $exception) { // Stop store emulation process $appEmulation->stopEnvironmentEmulation($initialEnvironmentInfo); throw $exception; } // Stop store emulation process $appEmulation->stopEnvironmentEmulation($initialEnvironmentInfo); // Retrieve corresponding email template id and customer name if ($order->getCustomerIsGuest()) { $templateId = Mage::getStoreConfig(self::XML_PATH_EMAIL_GUEST_TEMPLATE, $storeId); $customerName = $order->getBillingAddress()->getName(); } else { $templateId = Mage::getStoreConfig(self::XML_PATH_EMAIL_TEMPLATE, $storeId); $customerName = $order->getCustomerName(); } $mailer = Mage::getModel('core/email_template_mailer'); if ($notifyCustomer) { $emailInfo = Mage::getModel('core/email_info'); $emailInfo->addTo($order->getCustomerEmail(), $customerName); if ($copyTo && $copyMethod == 'bcc') { // Add bcc to customer email foreach ($copyTo as $email) { $emailInfo->addBcc($email); } } $mailer->addEmailInfo($emailInfo); } // Email copies are sent as separated emails if their copy method is 'copy' or a customer should not be notified if ($copyTo && ($copyMethod == 'copy' || !$notifyCustomer)) { foreach ($copyTo as $email) { $emailInfo = Mage::getModel('core/email_info'); $emailInfo->addTo($email); $mailer->addEmailInfo($emailInfo); } } // Set all required params and send emails $mailer->setSender(Mage::getStoreConfig(self::XML_PATH_EMAIL_IDENTITY, $storeId)); $mailer->setStoreId($storeId); $mailer->setTemplateId($templateId); $mailer->setTemplateParams(array( 'order' => $order, 'creditmemo' => $this, 'comment' => $comment, 'billing' => $order->getBillingAddress(), 'payment_html' => $paymentBlockHtml ) ); $mailer->send(); $this->setEmailSent(true); $this->_getResource()->saveAttribute($this, 'email_sent'); return $this; } /** * Send email with creditmemo update information * * @param boolean $notifyCustomer * @param string $comment * @return Mage_Sales_Model_Order_Creditmemo */ public function sendUpdateEmail($notifyCustomer = true, $comment = '') { $order = $this->getOrder(); $storeId = $order->getStore()->getId(); if (!Mage::helper('sales')->canSendCreditmemoCommentEmail($storeId)) { return $this; } // Get the destination email addresses to send copies to $copyTo = $this->_getEmails(self::XML_PATH_UPDATE_EMAIL_COPY_TO); $copyMethod = Mage::getStoreConfig(self::XML_PATH_UPDATE_EMAIL_COPY_METHOD, $storeId); // Check if at least one recepient is found if (!$notifyCustomer && !$copyTo) { return $this; } // Retrieve corresponding email template id and customer name if ($order->getCustomerIsGuest()) { $templateId = Mage::getStoreConfig(self::XML_PATH_UPDATE_EMAIL_GUEST_TEMPLATE, $storeId); $customerName = $order->getBillingAddress()->getName(); } else { $templateId = Mage::getStoreConfig(self::XML_PATH_UPDATE_EMAIL_TEMPLATE, $storeId); $customerName = $order->getCustomerName(); } $mailer = Mage::getModel('core/email_template_mailer'); if ($notifyCustomer) { $emailInfo = Mage::getModel('core/email_info'); $emailInfo->addTo($order->getCustomerEmail(), $customerName); if ($copyTo && $copyMethod == 'bcc') { // Add bcc to customer email foreach ($copyTo as $email) { $emailInfo->addBcc($email); } } $mailer->addEmailInfo($emailInfo); } // Email copies are sent as separated emails if their copy method is 'copy' or a customer should not be notified if ($copyTo && ($copyMethod == 'copy' || !$notifyCustomer)) { foreach ($copyTo as $email) { $emailInfo = Mage::getModel('core/email_info'); $emailInfo->addTo($email); $mailer->addEmailInfo($emailInfo); } } // Set all required params and send emails $mailer->setSender(Mage::getStoreConfig(self::XML_PATH_UPDATE_EMAIL_IDENTITY, $storeId)); $mailer->setStoreId($storeId); $mailer->setTemplateId($templateId); $mailer->setTemplateParams(array( 'order' => $order, 'creditmemo' => $this, 'comment' => $comment, 'billing' => $order->getBillingAddress() ) ); $mailer->send(); return $this; } protected function _getEmails($configPath) { $data = Mage::getStoreConfig($configPath, $this->getStoreId()); if (!empty($data)) { return explode(',', $data); } return false; } protected function _beforeDelete() { $this->_protectFromNonAdmin(); return parent::_beforeDelete(); } /** * After save object manipulations * * @return Mage_Sales_Model_Order_Creditmemo */ protected function _afterSave() { if (null != $this->_items) { foreach ($this->_items as $item) { $item->save(); } } if (null != $this->_comments) { foreach($this->_comments as $comment) { $comment->save(); } } return parent::_afterSave(); } /** * Before object save manipulations * * @return Mage_Sales_Model_Order_Creditmemo */ protected function _beforeSave() { parent::_beforeSave(); if (!$this->getOrderId() && $this->getOrder()) { $this->setOrderId($this->getOrder()->getId()); $this->setBillingAddressId($this->getOrder()->getBillingAddress()->getId()); } return $this; } /** * Get creditmemos collection filtered by $filter * * @param array|null $filter * @return Mage_Sales_Model_Resource_Order_Creditmemo_Collection */ public function getFilteredCollectionItems($filter = null) { return $this->getResourceCollection()->getFiltered($filter); } /** * Checking if the credit memo is last * * @return bool */ public function isLast() { foreach ($this->getAllItems() as $item) { if (!$item->isLast()) { return false; } } return true; } }