diff --git a/Service/Order/Import.php b/Service/Order/Import.php index 4c954ce5..9110907b 100755 --- a/Service/Order/Import.php +++ b/Service/Order/Import.php @@ -156,18 +156,20 @@ public function execute(ChannableOrderData $orderData): OrderInterface $order->setTransactionFee($quote->getTransactionFee()); if (isset($orderData['price']['discount']) && !empty((float)$orderData['price']['discount'])) { - $orderCurrency = $orderData['price']['currency'] ?? ''; - $discountAmount = abs((float)$orderData['price']['discount']); - $baseDiscountAmount = $this->currencyConverter->convertToBase( - $discountAmount, - $orderCurrency, - $storeId - ); - $order->setDiscountDescription($orderData['channel_name']); - $order->setBaseDiscountAmount($baseDiscountAmount * -1); - $order->setDiscountAmount($discountAmount * -1); - $order->setGrandTotal($order->getGrandTotal() - $discountAmount); - $order->setBaseGrandTotal($order->getBaseGrandTotal() - $baseDiscountAmount); + if (!$this->hasItemLevelDiscounts($orderData)) { + $orderCurrency = $orderData['price']['currency'] ?? ''; + $discountAmount = abs((float)$orderData['price']['discount']); + $baseDiscountAmount = $this->currencyConverter->convertToBase( + $discountAmount, + $orderCurrency, + $storeId + ); + $order->setDiscountDescription($orderData['channel_name']); + $order->setBaseDiscountAmount($baseDiscountAmount * -1); + $order->setDiscountAmount($discountAmount * -1); + $order->setGrandTotal($order->getGrandTotal() - $discountAmount); + $order->setBaseGrandTotal($order->getBaseGrandTotal() - $baseDiscountAmount); + } } $store->setCurrentCurrencyCode($store->getBaseCurrencyCode()); @@ -198,6 +200,19 @@ public function execute(ChannableOrderData $orderData): OrderInterface } } + /** + * Check if any product in the order data has an item-level discount. + */ + private function hasItemLevelDiscounts(array $orderData): bool + { + foreach ($orderData['products'] ?? [] as $item) { + if (isset($item['discount']) && (float)$item['discount'] > 0) { + return true; + } + } + return false; + } + /** * Add shipping info to the checkout-session * diff --git a/Service/Order/Items/Add.php b/Service/Order/Items/Add.php index 71d7fbfd..618a5b1b 100644 --- a/Service/Order/Items/Add.php +++ b/Service/Order/Items/Add.php @@ -131,7 +131,11 @@ public function execute(Quote $quote, array $data, StoreInterface $store, bool $ throw new CouldNotImportOrder(__($addedItem)); } - $addedItem->setOriginalCustomPrice($price); + $itemDiscount = (isset($item['discount']) && (float)$item['discount'] > 0) + ? (float)$item['discount'] + : 0.0; + + $addedItem->setOriginalCustomPrice($price - $itemDiscount); $addedItem->setOriginalPrice($channableBasePrice); $this->itemResourceModel->save($addedItem); $qty += (int)$item['quantity']; diff --git a/Test/End-2-end/support/services/ChannableApi.ts b/Test/End-2-end/support/services/ChannableApi.ts index 070f90a9..1b76b9af 100644 --- a/Test/End-2-end/support/services/ChannableApi.ts +++ b/Test/End-2-end/support/services/ChannableApi.ts @@ -107,6 +107,7 @@ export default class ChannableApi extends BaseApi { businessOrder?: boolean; shipping?: number; discount?: number; + itemDiscount?: number; companyName?: string; channelName?: string; shipmentMethod?: string; @@ -161,6 +162,10 @@ export default class ChannableApi extends BaseApi { data.products[0].price_tax = priceTax; } + if (overrides.itemDiscount !== undefined) { + data.products[0].discount = overrides.itemDiscount; + } + if (overrides.orderStatus) { data.order_status = overrides.orderStatus; } diff --git a/Test/End-2-end/tests/order/order-import.spec.ts b/Test/End-2-end/tests/order/order-import.spec.ts index 54489c9f..9e3f7896 100644 --- a/Test/End-2-end/tests/order/order-import.spec.ts +++ b/Test/End-2-end/tests/order/order-import.spec.ts @@ -188,6 +188,43 @@ const testCases = [ expect(displayedId).toBeTruthy(); }, }, + { + title: 'Item-level discount: original price vs discounted price', + config: {}, + orderOverrides: { price: 24.99, itemDiscount: 2.50, discount: 2.50 }, + assert: async (page, incrementId) => { + // Original price should be the full Channable price (24.99) + const originalPriceStr = await orderViewPage.getOriginalPrice(page); + const originalPrice = parsePrice(originalPriceStr); + expect(originalPrice).toBeCloseTo(24.99, 1); + + // Item price should be lower than original (discount applied) + const itemPriceStr = await orderViewPage.getItemPrice(page); + const itemPrice = parsePrice(itemPriceStr); + expect(itemPrice).toBeLessThan(originalPrice); + + // Grand total must equal discounted price (22.49), not full price (24.99) + const grandTotalStr = await orderViewPage.getGrandTotal(page); + const grandTotal = parsePrice(grandTotalStr); + expect(grandTotal).toBeCloseTo(22.49, 1); + }, + }, + { + title: 'Item-level discount: multi-qty grand total', + config: {}, + orderOverrides: { price: 24.99, quantity: 3, itemDiscount: 2.50, discount: 7.50 }, + assert: async (page, incrementId) => { + // Grand total should be 3 * (24.99 - 2.50) = 67.47 + const grandTotalStr = await orderViewPage.getGrandTotal(page); + const grandTotal = parsePrice(grandTotalStr); + expect(grandTotal).toBeCloseTo(67.47, 1); + + // Row total must be less than undiscounted (3 * 24.99 = 74.97) + const rowTotalStr = await orderViewPage.getRowTotal(page); + const rowTotal = parsePrice(rowTotalStr); + expect(rowTotal).toBeLessThan(74.97); + }, + }, { title: 'Multi-currency order (PLN)', config: {