Skip to content
Draft
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
18 changes: 16 additions & 2 deletions Model/Api.php
Original file line number Diff line number Diff line change
Expand Up @@ -372,8 +372,15 @@ public function lookupTaxes($itemsByType, $shippingAssignment, $quote)
// Skip products with tax_class_id of None, store owners should avoid doing this
continue;
}

// Create unique ItemID by combining SKU with quote item ID to distinguish between
// separate line items (e.g., regular item vs promo item with same SKU)
// We use the quote item ID (not tax calculation ID) so it matches returnOrder
$quoteItemId = $item->getId();
$itemId = $quoteItemId ? $item->getSku() . '-' . $quoteItemId : $item->getSku();

$cartItems[] = array(
'ItemID' => $item->getSku(),
'ItemID' => $itemId,
'Index' => $index,
'TIC' => $this->productTicService->getProductTic($item, 'lookupTaxes'),
'Price' => $item->getPrice() - $item->getDiscountAmount() / $item->getQty(),
Expand Down Expand Up @@ -796,8 +803,15 @@ public function returnOrder($creditmemo)
if ($items) {
foreach ($items as $creditItem) {
$item = $creditItem->getOrderItem();

// Create unique ItemID by combining SKU with quote item ID to distinguish between
// separate line items (e.g., regular item vs promo item with same SKU)
// We use quote item ID (from order item) to match the ItemID used in lookupTaxes
$quoteItemId = $item->getQuoteItemId();
$itemId = $quoteItemId ? $item->getSku() . '-' . $quoteItemId : $item->getSku();

$cartItems[] = array(
'ItemID' => $item->getSku(),
'ItemID' => $itemId,
'Index' => $index,
'TIC' => $this->productTicService->getProductTic($item, 'returnOrder'),
'Price' => $creditItem->getPrice() - $creditItem->getDiscountAmount() / $creditItem->getQty(),
Expand Down
2 changes: 2 additions & 0 deletions Test/Unit/Mocks/MagentoMocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ public function getSku() { return null; }
public function setSku($sku) { return $this; }
public function getProduct() { return null; }
public function setProduct($product) { return $this; }
public function getQuoteItemId() { return null; }
public function getId() { return null; }
}

class Order
Expand Down
150 changes: 147 additions & 3 deletions Test/Unit/Model/ApiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ public function testReturnOrderWithCartItems()
$creditItem->method('getQty')->willReturn(1);

$orderItem->method('getSku')->willReturn('TEST_SKU');
$orderItem->method('getQuoteItemId')->willReturn(12345); // Quote item ID for ItemID consistency
$orderItem->method('getProduct')->willReturn($product);

$product->method('getId')->willReturn(1);
Expand All @@ -260,15 +261,19 @@ public function testReturnOrderWithCartItems()
$this->mockSoapClient->method('Returned')
->willReturn($mockResponse);

// Mock data object methods
$this->mockDataObject->method('setParams')->willReturnSelf();
// Mock data object methods - capture params to verify ItemID format
$capturedParams = null;
$this->mockDataObject->method('setParams')->willReturnCallback(function($params) use (&$capturedParams) {
$capturedParams = $params;
return $this->mockDataObject;
});
$this->mockDataObject->method('getParams')->willReturn([
'apiLoginID' => 'test_api_id',
'apiKey' => 'test_api_key',
'orderID' => 'TEST_ORDER_123',
'cartItems' => [
[
'ItemID' => 'TEST_SKU',
'ItemID' => 'TEST_SKU-12345',
'Index' => 0,
'TIC' => '20000',
'Price' => 14.99,
Expand Down Expand Up @@ -296,6 +301,14 @@ public function testReturnOrderWithCartItems()

// Assert the result
$this->assertTrue($result, 'returnOrder should return true for successful refund with items');

// Verify ItemID format includes quote item ID for uniqueness
if ($capturedParams && isset($capturedParams['cartItems'][0]['ItemID'])) {
$itemId = $capturedParams['cartItems'][0]['ItemID'];
$this->assertStringContainsString('TEST_SKU', $itemId, 'ItemID should contain SKU');
$this->assertStringContainsString('12345', $itemId, 'ItemID should contain quote item ID');
$this->assertEquals('TEST_SKU-12345', $itemId, 'ItemID should be formatted as SKU-QuoteItemID');
}
}

/**
Expand Down Expand Up @@ -355,4 +368,135 @@ public function testReturnOrderFailsWhenParameterIsLost()
// The test expects the method to return false due to the SOAP error
$this->assertFalse($result, 'returnOrder should return false when returnCoDeliveryFeeWhenNoCartItems parameter is missing');
}

public function testReturnOrderWithMultipleItemsSameSku()
{
// Mock configuration
$this->scopeConfig->method('getValue')
->willReturnMap([
['tax/taxcloud_settings/enabled', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, null, '1'],
['tax/taxcloud_settings/logging', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, null, '1'],
['tax/taxcloud_settings/api_id', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, null, 'test_api_id'],
['tax/taxcloud_settings/api_key', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, null, 'test_api_key'],
['tax/taxcloud_settings/default_tic', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, null, '00000'],
['tax/taxcloud_settings/shipping_tic', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, null, '11010']
]);

// Mock SOAP client
$this->soapClientFactory->method('create')
->willReturn($this->mockSoapClient);

// Mock data object for event handling
$this->objectFactory->method('create')
->willReturn($this->mockDataObject);

// Mock credit memo with multiple items that have the same SKU
// This simulates a regular item and a promo item scenario
$creditmemo = $this->createMock(\Magento\Sales\Model\Order\Creditmemo::class);
$order = $this->createMock(\Magento\Sales\Model\Order\Order::class);
$order->method('getIncrementId')->willReturn('TEST_ORDER_123');

// First item: regular item
$creditItem1 = $this->createMock(\Magento\Sales\Model\Order\Creditmemo\Item::class);
$orderItem1 = $this->createMock(\Magento\Sales\Model\Order\Item::class);
$product1 = $this->createMock(\Magento\Catalog\Model\Product::class);

$creditItem1->method('getOrderItem')->willReturn($orderItem1);
$creditItem1->method('getPrice')->willReturn(120.00);
$creditItem1->method('getDiscountAmount')->willReturn(0);
$creditItem1->method('getQty')->willReturn(1);

$orderItem1->method('getSku')->willReturn('ABC123');
$orderItem1->method('getQuoteItemId')->willReturn(456); // Different quote item ID
$orderItem1->method('getProduct')->willReturn($product1);

// Second item: promo item with same SKU
$creditItem2 = $this->createMock(\Magento\Sales\Model\Order\Creditmemo\Item::class);
$orderItem2 = $this->createMock(\Magento\Sales\Model\Order\Item::class);
$product2 = $this->createMock(\Magento\Catalog\Model\Product::class);

$creditItem2->method('getOrderItem')->willReturn($orderItem2);
$creditItem2->method('getPrice')->willReturn(0.00);
$creditItem2->method('getDiscountAmount')->willReturn(0);
$creditItem2->method('getQty')->willReturn(1);

$orderItem2->method('getSku')->willReturn('ABC123'); // Same SKU!
$orderItem2->method('getQuoteItemId')->willReturn(789); // Different quote item ID
$orderItem2->method('getProduct')->willReturn($product2);

$product1->method('getId')->willReturn(1);
$product2->method('getId')->willReturn(1);

$productModel = $this->createMock(\Magento\Catalog\Model\Product::class);
$customAttribute = $this->createMock(\Magento\Framework\Api\AttributeValue::class);
$productModel->method('load')->willReturnSelf();
$productModel->method('getCustomAttribute')->willReturn($customAttribute);
$customAttribute->method('getValue')->willReturn('20000');

$this->productFactory->method('create')->willReturn($productModel);

$creditmemo->method('getOrder')->willReturn($order);
$creditmemo->method('getAllItems')->willReturn([$creditItem1, $creditItem2]);
$creditmemo->method('getShippingAmount')->willReturn(0);

// Mock successful SOAP response
$mockResponse = new \stdClass();
$mockResponse->ReturnedResult = new \stdClass();
$mockResponse->ReturnedResult->ResponseType = 'OK';
$mockResponse->ReturnedResult->Messages = [];

$this->mockSoapClient->method('Returned')
->willReturn($mockResponse);

// Mock data object methods - capture params to verify ItemID uniqueness
$capturedParams = null;
$this->mockDataObject->method('setParams')->willReturnCallback(function($params) use (&$capturedParams) {
$capturedParams = $params;
return $this->mockDataObject;
});
$this->mockDataObject->method('getParams')->willReturn([
'apiLoginID' => 'test_api_id',
'apiKey' => 'test_api_key',
'orderID' => 'TEST_ORDER_123',
'cartItems' => [
[
'ItemID' => 'ABC123-456',
'Index' => 0,
'TIC' => '20000',
'Price' => 120.00,
'Qty' => 1
],
[
'ItemID' => 'ABC123-789',
'Index' => 1,
'TIC' => '20000',
'Price' => 0.00,
'Qty' => 1
]
],
'returnedDate' => '2025-01-03T00:00:00+00:00',
'returnCoDeliveryFeeWhenNoCartItems' => false
]);
$this->mockDataObject->method('setResult')->willReturnSelf();
$this->mockDataObject->method('getResult')->willReturn([
'ResponseType' => 'OK',
'Messages' => []
]);

// Execute the method
$result = $this->api->returnOrder($creditmemo);

// Assert the result
$this->assertTrue($result, 'returnOrder should return true for successful refund with multiple items');

// Verify ItemIDs are unique even with same SKU
if ($capturedParams && isset($capturedParams['cartItems'][0]['ItemID']) && isset($capturedParams['cartItems'][1]['ItemID'])) {
$itemId1 = $capturedParams['cartItems'][0]['ItemID'];
$itemId2 = $capturedParams['cartItems'][1]['ItemID'];

$this->assertNotEquals($itemId1, $itemId2, 'Items with same SKU should have unique ItemIDs');
$this->assertEquals('ABC123-456', $itemId1, 'First item should have ItemID with quote item ID 456');
$this->assertEquals('ABC123-789', $itemId2, 'Second item should have ItemID with quote item ID 789');
}
}
}
Loading