Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions app/code/Magento/Sales/Model/Order/Invoice.php
Original file line number Diff line number Diff line change
Expand Up @@ -713,7 +713,7 @@ public function addComment($comment, $notify = false, $visibleOnFront = false)
}
$comment->setInvoice($this)->setStoreId($this->getStoreId())->setParentId($this->getId());
if (!$comment->getId()) {
$this->getCommentsCollection()->addItem($comment);
$this->getCommentsCollection(true)->addItem($comment);
}
$this->_hasDataChanges = true;
return $this;
Expand All @@ -727,24 +727,28 @@ public function addComment($comment, $notify = false, $visibleOnFront = false)
*/
public function getCommentsCollection($reload = false)
{
if (!$this->hasData(InvoiceInterface::COMMENTS) || $reload) {
$data = $this->getData(InvoiceInterface::COMMENTS);

if (!$data instanceof \Magento\Sales\Model\ResourceModel\Order\Invoice\Comment\Collection || $reload) {
$comments = $this->_commentCollectionFactory->create()->setInvoiceFilter($this->getId())
->setCreatedAtOrder();

$this->setComments($comments);
/**
* When invoice created with adding comment, comments collection
* When invoice created with adding comment, a comments collection
* must be loaded before we added this comment.
*/
$this->getComments()->load();
$comments->load();

if ($this->getId()) {
foreach ($this->getComments() as $comment) {
foreach ($comments as $comment) {
$comment->setInvoice($this);
}
}

return $comments;
}
return $this->getComments();
return $data;
}

/**
Expand Down Expand Up @@ -808,13 +812,20 @@ public function getItems()
*/
public function getComments()
{
if ($this->getData(InvoiceInterface::COMMENTS) === null && $this->getId()) {
$data = $this->getData(InvoiceInterface::COMMENTS);

if ($data instanceof \Magento\Sales\Model\ResourceModel\Order\Invoice\Comment\Collection) {
return $data->getItems();
}

if ($data === null && $this->getId()) {
$collection = $this->_commentCollectionFactory->create()->setInvoiceFilter($this->getId());
foreach ($collection as $comment) {
$comment->setInvoice($this);
}
$this->setData(InvoiceInterface::COMMENTS, $collection->getItems());
}

return $this->getData(InvoiceInterface::COMMENTS);
}

Expand Down
79 changes: 79 additions & 0 deletions app/code/Magento/Sales/Test/Unit/Model/Order/InvoiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -488,4 +488,83 @@ public static function getNotOpenedInvoiceStatuses()
[Invoice::STATE_CANCELED, Invoice::STATE_CANCELED],
];
}

/**
* Test that getComments() and getCommentsCollection() work together without type conflicts
*
* This test verifies the fix for the issue where calling getComments() before addComment()
* would cause a fatal error because getComments() stores an array while
* getCommentsCollection() expects a Collection object.
*/
public function testCommentsAndCommentsCollectionTypeCompatibility()
{
$commentCollection = $this->getMockBuilder(
\Magento\Sales\Model\ResourceModel\Order\Invoice\Comment\Collection::class
)->disableOriginalConstructor()
->onlyMethods(['setInvoiceFilter', 'setCreatedAtOrder', 'load', 'getItems', 'getIterator'])
->getMock();
$commentCollection->expects($this->any())
->method('setInvoiceFilter')
->willReturnSelf();
$commentCollection->expects($this->any())
->method('setCreatedAtOrder')
->willReturnSelf();
$commentCollection->expects($this->any())
->method('load')
->willReturnSelf();
$commentCollection->expects($this->any())
->method('getItems')
->willReturn([]);
$commentCollection->expects($this->any())
->method('getIterator')
->willReturn(new \ArrayIterator([]));

$commentCollectionFactory = $this->createMock(CommentCollectionFactory::class);
$commentCollectionFactory->expects($this->any())
->method('create')
->willReturn($commentCollection);

$objectManager = new ObjectManager($this);
$invoice = $objectManager->getObject(
Invoice::class,
[
'commentCollectionFactory' => $commentCollectionFactory,
]
);
$invoice->setId(1);

// Test scenario 1: getComments() returns array
$comments = $invoice->getComments();
$this->assertIsArray($comments, 'getComments() should return an array');

// Test scenario 2: getCommentsCollection() returns Collection even after getComments() stored array
$collection = $invoice->getCommentsCollection();
$this->assertInstanceOf(
\Magento\Sales\Model\ResourceModel\Order\Invoice\Comment\Collection::class,
$collection,
'getCommentsCollection() should return Collection instance even after getComments() was called'
);

// Test scenario 3: getComments() returns array even after getCommentsCollection() stored Collection
$invoice->getCommentsCollection();
$commentsAfterCollection = $invoice->getComments();
$this->assertIsArray(
$commentsAfterCollection,
'getComments() should always return array, even after getCommentsCollection() was called'
);

// Test scenario 4: Multiple calls maintain type consistency
$collection2 = $invoice->getCommentsCollection();
$this->assertInstanceOf(
\Magento\Sales\Model\ResourceModel\Order\Invoice\Comment\Collection::class,
$collection2,
'getCommentsCollection() should consistently return Collection instance'
);

$comments2 = $invoice->getComments();
$this->assertIsArray(
$comments2,
'getComments() should consistently return array'
);
}
}