diff --git a/packages/template-retail-react-app/CHANGELOG.md b/packages/template-retail-react-app/CHANGELOG.md
index 8d3da4cd77..846880969f 100644
--- a/packages/template-retail-react-app/CHANGELOG.md
+++ b/packages/template-retail-react-app/CHANGELOG.md
@@ -5,6 +5,7 @@
- Integrate Order Details page to display orders data from OMS [#3573](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3573)
- Integrate Order History page to display data from OMS [#3581](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3581)
- Add shipping display support for OMS [#3588](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3588)
+ - BOPIS multishipment with OMS [#3613] (https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3613)
- [Feature] Update passwordless login and password reset to use email mode by default. The mode can now be configured across the login page, auth modal, and checkout page [#3525](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3525)
- Update "Continue Securely" button text to "Continue" for passwordless login [#3556](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3556)
diff --git a/packages/template-retail-react-app/app/pages/account/order-detail.jsx b/packages/template-retail-react-app/app/pages/account/order-detail.jsx
index 27733626f1..b5987f0cfc 100644
--- a/packages/template-retail-react-app/app/pages/account/order-detail.jsx
+++ b/packages/template-retail-react-app/app/pages/account/order-detail.jsx
@@ -134,6 +134,15 @@ const AccountOrderDetail = () => {
)
const isLoading = isOrderLoading || !order
+ // Check if order has OMS data
+ const isOmsOrder = useMemo(() => !!order?.omsData, [order?.omsData])
+
+ // Check if order is multi-shipment order
+ const isMultiShipmentOrder = useMemo(
+ () => (order?.omsData?.shipments?.length ?? 0) > 1 || (order?.shipments?.length ?? 0) > 1,
+ [isOmsOrder, order?.omsData?.shipments?.length, order?.shipments?.length]
+ )
+
const {pickupShipments, deliveryShipments} = useMemo(() => {
return storeLocatorEnabled
? groupShipmentsByDeliveryOption(order)
@@ -164,6 +173,67 @@ const AccountOrderDetail = () => {
[storeData?.data]
)
+ const renderShippingMethod = (
+ shippingMethodName,
+ shippingStatus,
+ trackingNumber,
+ trackingUrl,
+ shipmentsLength,
+ index
+ ) => (
+
+
+ {shipmentsLength > 1 ? (
+
+ ) : (
+
+ )}
+
+
+
+ {{
+ not_shipped: formatMessage({
+ defaultMessage: 'Not shipped',
+ id: 'account_order_detail.shipping_status.not_shipped'
+ }),
+ part_shipped: formatMessage({
+ defaultMessage: 'Partially shipped',
+ id: 'account_order_detail.shipping_status.part_shipped'
+ }),
+ shipped: formatMessage({
+ defaultMessage: 'Shipped',
+ id: 'account_order_detail.shipping_status.shipped'
+ })
+ }[shippingStatus] || shippingStatus}
+
+ {shippingMethodName}
+ {trackingNumber && (
+
+
+ :{' '}
+ {trackingUrl ? (
+
+ {trackingNumber}
+
+ ) : (
+ trackingNumber
+ )}
+
+ )}
+
+
+ )
+
const paymentCard = order?.paymentInstruments?.[0]?.paymentCard
const CardIcon = getCreditCardIcon(paymentCard?.cardType)
const itemCount = order?.productItems?.reduce((count, item) => item.quantity + count, 0) || 0
@@ -325,86 +395,31 @@ const AccountOrderDetail = () => {
)
})}
+ {/* Any type of Non-OMS or any type of single shipment order: show DeliveryMethods and Shipments info*/}
+ {(!isOmsOrder || !isMultiShipmentOrder) &&
+ deliveryShipments.map((shipment, index) => {
+ const omsShipment = isOmsOrder
+ ? order.omsData.shipments?.[index]
+ : null
- {/* Delivery Shipments */}
- {deliveryShipments.map((shipment, index) => {
- // Hide shipping address for OMS multi-shipment orders
- // Can't reliably correlate OMS shipments to ECOM addresses by index
- const isOmsOrder = !!order?.omsData
- const omsShipment = isOmsOrder
- ? order.omsData.shipments?.[index]
- : null
- const isOmsMultiShipment =
- isOmsOrder &&
- (order.omsData.shipments?.length > 1 ||
- order.shipments?.length > 1)
-
- const shippingMethodName =
- omsShipment?.provider || shipment.shippingMethod.name
- const shippingStatus =
- omsShipment?.status || shipment.shippingStatus
- const trackingNumber =
- omsShipment?.trackingNumber || shipment.trackingNumber
- const trackingUrl = omsShipment?.trackingUrl
+ const shippingMethodName =
+ omsShipment?.provider || shipment.shippingMethod.name
+ const shippingStatus =
+ omsShipment?.status || shipment.shippingStatus
+ const trackingNumber =
+ omsShipment?.trackingNumber || shipment.trackingNumber
+ const trackingUrl = omsShipment?.trackingUrl
- return (
-
-
-
- {deliveryShipments.length > 1 ? (
-
- ) : (
-
- )}
-
-
-
- {{
- not_shipped: formatMessage({
- defaultMessage: 'Not shipped',
- id: 'account_order_detail.shipping_status.not_shipped'
- }),
- part_shipped: formatMessage({
- defaultMessage: 'Partially shipped',
- id: 'account_order_detail.shipping_status.part_shipped'
- }),
- shipped: formatMessage({
- defaultMessage: 'Shipped',
- id: 'account_order_detail.shipping_status.shipped'
- })
- }[shippingStatus] || shippingStatus}
-
- {shippingMethodName}
- {trackingNumber && (
-
-
- :{' '}
- {trackingUrl ? (
-
- {trackingNumber}
-
- ) : (
- trackingNumber
- )}
-
- )}
-
-
- {!isOmsMultiShipment && (
+ return (
+
+ {renderShippingMethod(
+ shippingMethodName,
+ shippingStatus,
+ trackingNumber,
+ trackingUrl,
+ deliveryShipments.length,
+ index
+ )}
{deliveryShipments.length > 1 ? (
@@ -437,10 +452,25 @@ const AccountOrderDetail = () => {
+
+ )
+ })}
+
+ {/* Any OMS multi-shipment: Only show OMS Shipments info;*/}
+ {isOmsOrder &&
+ isMultiShipmentOrder &&
+ order?.omsData?.shipments?.map((shipment, index) => (
+
+ {renderShippingMethod(
+ shipment.provider,
+ shipment.status,
+ shipment.trackingNumber,
+ shipment.trackingUrl,
+ order?.omsData?.shipments?.length ?? 0,
+ index
)}
- )
- })}
+ ))}
{/* Payment Method */}
{paymentCard && (
diff --git a/packages/template-retail-react-app/app/pages/account/orders.test.js b/packages/template-retail-react-app/app/pages/account/orders.test.js
index 09d586b66a..051c16b0e2 100644
--- a/packages/template-retail-react-app/app/pages/account/orders.test.js
+++ b/packages/template-retail-react-app/app/pages/account/orders.test.js
@@ -795,7 +795,7 @@ describe('OMS Single shipment with partial data (missing provider, trackingUrl)'
})
})
-describe('BOPIS Order with OMS - Pickup and Delivery', () => {
+describe('BOPIS Order with OMS Single Pickup and Single Delivery', () => {
// Helper to create BOPIS order with OMS data
const createBopisOmsOrder = (overrides = {}) => ({
orderNo: 'BOPIS-OMS-001',
@@ -825,7 +825,6 @@ describe('BOPIS Order with OMS - Pickup and Delivery', () => {
shippingMethod: {
name: 'Ground'
},
- trackingNumber: 'ECOM-TRACK-OLD',
shippingAddress: {
fullName: 'Sarah Johnson',
address1: '456 Delivery St',
@@ -887,18 +886,8 @@ describe('BOPIS Order with OMS - Pickup and Delivery', () => {
expect(await screen.findByText('Shipping Method')).toBeInTheDocument()
expect(await screen.findByText(/FedEx/i)).toBeInTheDocument()
expect(screen.queryByText(/Ground/i)).not.toBeInTheDocument()
- })
-
- test('should display OMS tracking number as clickable link for delivery shipment', async () => {
- setupOrderDetailsPage(createBopisOmsOrder())
- expect(await screen.findByTestId('account-order-details-page')).toBeInTheDocument()
const trackingLink = await screen.findByRole('link', {name: /BOPIS-TRACK-123/i})
expect(trackingLink).toHaveAttribute('href', 'https://tracking.fedex.com/BOPIS-TRACK-123')
- })
-
- test('should display OMS shipment status for delivery shipment', async () => {
- setupOrderDetailsPage(createBopisOmsOrder())
- expect(await screen.findByTestId('account-order-details-page')).toBeInTheDocument()
expect(await screen.findByText(/SHIPPED/i)).toBeInTheDocument()
})
@@ -1131,10 +1120,11 @@ describe('BOPIS Order with OMS - Multiple Pickup Locations', () => {
expect(await screen.findByText(/Partially Ready/i)).toBeInTheDocument()
})
- test('should display OMS provider and tracking for delivery shipment', async () => {
+ test('should display OMS shipment 1 with UPS provider and tracking', async () => {
setupOrderDetailsPage(createMultiPickupBopisOmsOrder())
expect(await screen.findByTestId('account-order-details-page')).toBeInTheDocument()
expect(await screen.findByText(/UPS/i)).toBeInTheDocument()
+ expect(await screen.findByText(/SHIPPED/i)).toBeInTheDocument()
const trackingLink = await screen.findByRole('link', {name: /MULTI-TRACK-456/i})
expect(trackingLink).toHaveAttribute(
'href',
@@ -1142,6 +1132,15 @@ describe('BOPIS Order with OMS - Multiple Pickup Locations', () => {
)
})
+ test('should display OMS shipment 2 with FedEx provider and tracking', async () => {
+ setupOrderDetailsPage(createMultiPickupBopisOmsOrder())
+ expect(await screen.findByTestId('account-order-details-page')).toBeInTheDocument()
+ expect(await screen.findByText(/FedEx/i)).toBeInTheDocument()
+ expect(await screen.findByText(/PENDING/i)).toBeInTheDocument()
+ const trackingLink = await screen.findByRole('link', {name: /MULTI-TRACK-789/i})
+ expect(trackingLink).toHaveAttribute('href', 'https://tracking.fedex.com/MULTI-TRACK-789')
+ })
+
test('should NOT display shipping address for multi-pickup BOPIS OMS order', async () => {
setupOrderDetailsPage(createMultiPickupBopisOmsOrder())
expect(await screen.findByTestId('account-order-details-page')).toBeInTheDocument()
@@ -1164,6 +1163,12 @@ describe('BOPIS Order with OMS - Pickup and Delivery Shipments', () => {
provider: 'FedEx',
trackingNumber: 'OMS-TRACK-456',
trackingUrl: 'https://tracking.fedex.com/OMS-TRACK-456'
+ },
+ {
+ status: 'NEW',
+ provider: 'UPS',
+ trackingNumber: 'OMS-TRACK-123',
+ trackingUrl: 'https://tracking.fedex.com/OMS-TRACK-123'
}
]
},
@@ -1180,8 +1185,6 @@ describe('BOPIS Order with OMS - Pickup and Delivery Shipments', () => {
shippingMethod: {
name: 'Ground'
},
- shippingStatus: 'shipped',
- trackingNumber: 'ECOM-TRACK-OLD',
shippingAddress: {
firstName: 'John',
lastName: 'Doe'
@@ -1230,31 +1233,27 @@ describe('BOPIS Order with OMS - Pickup and Delivery Shipments', () => {
expect(await screen.findByText(/100 Market St/i)).toBeInTheDocument()
})
- test('should display OMS provider for delivery shipment', async () => {
- setupOrderDetailsPage(createOmsBopisWithDelivery())
- expect(await screen.findByTestId('account-order-details-page')).toBeInTheDocument()
- expect(await screen.findByText(/FedEx/i)).toBeInTheDocument()
- expect(screen.queryByText(/Ground/i)).not.toBeInTheDocument()
- })
-
- test('should display OMS tracking number as clickable link for delivery', async () => {
+ test('should display OMS shipment 1 and link', async () => {
setupOrderDetailsPage(createOmsBopisWithDelivery())
expect(await screen.findByTestId('account-order-details-page')).toBeInTheDocument()
+ expect(await screen.findByText(/FEDEX/i)).toBeInTheDocument()
+ expect(await screen.findByText(/SHIPPED/i)).toBeInTheDocument()
const trackingLink = await screen.findByRole('link', {name: /OMS-TRACK-456/i})
expect(trackingLink).toHaveAttribute('href', 'https://tracking.fedex.com/OMS-TRACK-456')
})
- test('should display OMS shipment status for delivery', async () => {
+ test('should display OMS shipment 2 and link', async () => {
setupOrderDetailsPage(createOmsBopisWithDelivery())
expect(await screen.findByTestId('account-order-details-page')).toBeInTheDocument()
- expect(await screen.findByText(/SHIPPED/i)).toBeInTheDocument()
+ expect(await screen.findByText(/UPS/i)).toBeInTheDocument()
+ expect(await screen.findByText(/NEW/i)).toBeInTheDocument()
+ const trackingLink = await screen.findByRole('link', {name: /OMS-TRACK-123/i})
+ expect(trackingLink).toHaveAttribute('href', 'https://tracking.fedex.com/OMS-TRACK-123')
})
test('should NOT display shipping address for OMS BOPIS order with multiple shipments', async () => {
setupOrderDetailsPage(createOmsBopisWithDelivery())
expect(await screen.findByTestId('account-order-details-page')).toBeInTheDocument()
- // Shipping address should be hidden for OMS multi-shipment orders
- // (order has 2 shipments: 1 pickup + 1 delivery, so order.shipments?.length > 1)
expect(screen.queryByRole('heading', {name: /^shipping address$/i})).not.toBeInTheDocument()
expect(screen.queryByText(/John Doe/i)).not.toBeInTheDocument()
})