diff --git a/app/code/Magento/Sales/Model/Order/Invoice.php b/app/code/Magento/Sales/Model/Order/Invoice.php index 014ad8fd5fe3a..3f2fa1f72f6e5 100644 --- a/app/code/Magento/Sales/Model/Order/Invoice.php +++ b/app/code/Magento/Sales/Model/Order/Invoice.php @@ -407,6 +407,9 @@ public function void() */ public function cancel() { + if (!$this->canCancel()) { + return $this; + } $order = $this->getOrder(); $order->getPayment()->cancelInvoice($this); foreach ($this->getAllItems() as $item) { diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/InvoiceTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/InvoiceTest.php index 0962e32dfb6ed..0517da8b85cf0 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/InvoiceTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/InvoiceTest.php @@ -8,6 +8,7 @@ namespace Magento\Sales\Test\Unit\Model\Order; +use Magento\Sales\Api\Data\InvoiceInterface; use Magento\Sales\Model\Order\Invoice; use Magento\Sales\Model\ResourceModel\OrderFactory; use Magento\Sales\Model\Order; @@ -72,7 +73,7 @@ protected function setUp() ->setMethods( [ 'getPayment', '__wakeup', 'load', 'setHistoryEntityName', 'getStore', 'getBillingAddress', - 'getShippingAddress' + 'getShippingAddress', 'getConfig', ] ) ->getMock(); @@ -83,7 +84,7 @@ protected function setUp() $this->paymentMock = $this->getMockBuilder( \Magento\Sales\Model\Order\Payment::class )->disableOriginalConstructor()->setMethods( - ['canVoid', '__wakeup', 'canCapture', 'capture', 'pay'] + ['canVoid', '__wakeup', 'canCapture', 'capture', 'pay', 'cancelInvoice'] )->getMock(); $this->orderFactory = $this->createPartialMock(\Magento\Sales\Model\OrderFactory::class, ['create']); @@ -407,4 +408,58 @@ private function getOrderInvoiceCollection() return $collection; } + + /** + * Assert open invoice can be canceled, and its status changes + */ + public function testCancelOpenInvoice() + { + $orderConfigMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Config::class) + ->disableOriginalConstructor()->setMethods( + ['getStateDefaultStatus'] + )->getMock(); + $orderConfigMock->expects($this->once())->method('getStateDefaultStatus') + ->with(Order::STATE_PROCESSING) + ->willReturn(Order::STATE_PROCESSING); + $this->order->expects($this->once())->method('getPayment')->willReturn($this->paymentMock); + $this->order->expects($this->once())->method('getConfig')->willReturn($orderConfigMock); + $this->paymentMock->expects($this->once())->method('cancelInvoice')->willReturn($this->paymentMock); + $this->eventManagerMock->expects($this->once()) + ->method('dispatch') + ->with('sales_order_invoice_cancel'); + $this->model->setData(InvoiceInterface::ITEMS, []); + $this->model->setState(Invoice::STATE_OPEN); + $this->model->cancel(); + self::assertEquals(Invoice::STATE_CANCELED, $this->model->getState()); + } + + /** + * Assert open invoice can be canceled, and its status changes + * + * @param $initialInvoiceStatus + * @param $finalInvoiceStatus + * @dataProvider getNotOpenedInvoiceStatuses + */ + public function testCannotCancelNotOpenedInvoice($initialInvoiceStatus, $finalInvoiceStatus) + { + $this->order->expects($this->never())->method('getPayment'); + $this->paymentMock->expects($this->never())->method('cancelInvoice'); + $this->eventManagerMock->expects($this->never()) + ->method('dispatch') + ->with('sales_order_invoice_cancel'); + $this->model->setState($initialInvoiceStatus); + $this->model->cancel(); + self::assertEquals($finalInvoiceStatus, $this->model->getState()); + } + + /** + * @return array + */ + public function getNotOpenedInvoiceStatuses() + { + return [ + [Invoice::STATE_PAID, Invoice::STATE_PAID], + [Invoice::STATE_CANCELED, Invoice::STATE_CANCELED], + ]; + } }