From 3924e36b7f2cf6321c725d25a9f1e07bfa751723 Mon Sep 17 00:00:00 2001 From: dylandepass Date: Tue, 4 Aug 2026 23:36:36 -0400 Subject: [PATCH] fix(order-status): mirror Magento status mapping --- src/actions/submit/index.js | 2 ++ src/order-status.js | 49 +++++++++++++++++++++++++++++++++++++ test/order-status.test.js | 42 +++++++++++++++++++++++++++++++ test/submit.test.js | 24 ++++++++++++++++++ 4 files changed, 117 insertions(+) create mode 100644 src/order-status.js create mode 100644 test/order-status.test.js diff --git a/src/actions/submit/index.js b/src/actions/submit/index.js index 78f4cbd..799566f 100644 --- a/src/actions/submit/index.js +++ b/src/actions/submit/index.js @@ -3,6 +3,7 @@ import { publishEvent } from '../../events.js'; import makeContext from '../../context.js'; import { createProductRegistration, queryOrder } from '../../ebs.js'; import { proxyFetch } from '../../proxy.js'; +import { deriveOrderStatus } from '../../order-status.js'; const MAX_PAYLOAD_SIZE = 16_000; // 16KB @@ -233,6 +234,7 @@ async function handleOrderStatus(ctx, formId, data) { body.outcome = 'Partially Cancelled'; } } + body.order.status = deriveOrderStatus(lineItems); // remove PII from data delete body.order?.customer; diff --git a/src/order-status.js b/src/order-status.js new file mode 100644 index 0000000..4ed8049 --- /dev/null +++ b/src/order-status.js @@ -0,0 +1,49 @@ +/** + * Derive the customer-facing order status from EBS line items. + * + * This intentionally mirrors Magento's Vitamix_OrderStatus helper: + * warranty/service lines (UnitOfMeasure != Each) are excluded, cancelled + * lines are removed from the active-item denominator, and the final status + * is selected in shipped -> processed -> received order. + * + * @param {object[]} lineItems - Transformed EBS line items + * @returns {string} Status key for the storefront + */ +export function deriveOrderStatus(lineItems) { + const items = (Array.isArray(lineItems) ? lineItems : []).filter( + (item) => item?.unitOfMeasure === 'Each', + ); + + let received = 0; + let processed = 0; + let cancelled = 0; + let shipped = 0; + + items.forEach((item) => { + const status = String(item?.status ?? '').toUpperCase(); + const quantity = item?.quantity; + + if (status === 'ENTERED') { + received += 1; + } else if (['BOOKED', 'AWAITINGSHIPPING', 'PICKED'].includes(status)) { + processed += 1; + } else if (status === 'CLOSED') { + if (quantity != null && Number(quantity) === 0) { + cancelled += 1; + } else { + shipped += 1; + } + } else if (status === 'SHIPPED') { + shipped += 1; + } + }); + + const activeItemCount = items.length - cancelled; + + if (cancelled > 0 && cancelled === items.length) return 'cancelled'; + if (shipped === activeItemCount) return 'shipped'; + if (shipped > 0) return 'partiallyShipped'; + if (processed === activeItemCount) return 'processed'; + if (processed + received === activeItemCount) return 'received'; + return 'unavailable'; +} diff --git a/test/order-status.test.js b/test/order-status.test.js new file mode 100644 index 0000000..8904f9b --- /dev/null +++ b/test/order-status.test.js @@ -0,0 +1,42 @@ +import { deriveOrderStatus } from '../src/order-status.js'; + +describe('deriveOrderStatus', () => { + test('ignores non-Each service lines', () => { + expect(deriveOrderStatus([ + { unitOfMeasure: 'Years', status: 'Entered', quantity: '1' }, + ])).toBe('shipped'); + }); + + test.each([ + ['Entered', 'received'], + ['Booked', 'processed'], + ['AwaitingShipping', 'processed'], + ['Picked', 'processed'], + ['Shipped', 'shipped'], + ['Closed', 'shipped'], + ])('maps %s to %s', (status, expected) => { + expect(deriveOrderStatus([ + { unitOfMeasure: 'Each', status, quantity: '1' }, + ])).toBe(expected); + }); + + test('treats Closed quantity zero as cancelled', () => { + expect(deriveOrderStatus([ + { unitOfMeasure: 'Each', status: 'Closed', quantity: '0' }, + ])).toBe('cancelled'); + }); + + test('evaluates remaining items after cancelled items are excluded', () => { + expect(deriveOrderStatus([ + { unitOfMeasure: 'Each', status: 'Closed', quantity: '0' }, + { unitOfMeasure: 'Each', status: 'Booked', quantity: '1' }, + ])).toBe('processed'); + }); + + test('returns partiallyShipped when some active items shipped', () => { + expect(deriveOrderStatus([ + { unitOfMeasure: 'Each', status: 'Shipped', quantity: '1' }, + { unitOfMeasure: 'Each', status: 'Booked', quantity: '1' }, + ])).toBe('partiallyShipped'); + }); +}); diff --git a/test/submit.test.js b/test/submit.test.js index ae049b7..873bfab 100644 --- a/test/submit.test.js +++ b/test/submit.test.js @@ -309,6 +309,30 @@ describe('submit action', () => { expect(result.body.order.delivery).toHaveLength(1); }); + test('returns the Magento-compatible status derived from EBS line items', async () => { + const bookedBody = { + Response: { + '@_Id': 'booked-order', + '@_Outcome': 'Success', + '@_Succeeded': 'true', + 'Order': { + '@_Key': 'om-booked', + 'LineItem': [ + { '@_UnitOfMeasure': 'Each', '@_Status': 'Booked', '@_Quantity': '1' }, + { '@_UnitOfMeasure': 'Years', '@_Status': 'Entered', '@_Quantity': '1' }, + ], + }, + }, + }; + + mockMakeContext.mockResolvedValue(makeOrderCtx('om-booked')); + mockQueryOrder.mockResolvedValue({ status: 200, body: bookedBody }); + + const result = await main({}); + expect(result.body.order.status).toBe('processed'); + expect(result.body.order).not.toHaveProperty('lineItem'); + }); + test('omits PII fields from response', async () => { mockMakeContext.mockResolvedValue(makeOrderCtx()); mockQueryOrder.mockResolvedValue({ status: 200, body: successBody });