Selaa lähdekoodia

fix(core): Fix logic relating to partial fulfillments

Fixes #2324 fixes #2191
Michael Bromley 2 vuotta sitten
vanhempi
sitoutus
6f48ee284d

+ 47 - 0
packages/core/e2e/order.e2e-spec.ts

@@ -35,6 +35,7 @@ import * as Codegen from './graphql/generated-e2e-admin-types';
 import {
     AddManualPaymentDocument,
     CanceledOrderFragment,
+    CreateFulfillmentDocument,
     ErrorCode,
     FulfillmentFragment,
     GetOrderDocument,
@@ -47,6 +48,7 @@ import {
     RefundFragment,
     SortOrder,
     StockMovementType,
+    TransitFulfillmentDocument,
 } from './graphql/generated-e2e-admin-types';
 import * as CodegenShop from './graphql/generated-e2e-shop-types';
 import {
@@ -2609,6 +2611,51 @@ describe('Orders resolver', () => {
 
             expect(order!.state).toBe('PaymentSettled');
         });
+
+        // https://github.com/vendure-ecommerce/vendure/issues/2191
+        it('correctly transitions order & fulfillment on partial fulfillment being shipped', async () => {
+            await shopClient.asUserWithCredentials(customers[0].emailAddress, password);
+            const { addItemToOrder } = await shopClient.query<
+                CodegenShop.AddItemToOrderMutation,
+                CodegenShop.AddItemToOrderMutationVariables
+            >(ADD_ITEM_TO_ORDER, {
+                productVariantId: 'T_6',
+                quantity: 3,
+            });
+            await proceedToArrangingPayment(shopClient);
+            orderGuard.assertSuccess(addItemToOrder);
+
+            const order = await addPaymentToOrder(shopClient, singleStageRefundablePaymentMethod);
+            orderGuard.assertSuccess(order);
+
+            const { addFulfillmentToOrder } = await adminClient.query(CreateFulfillmentDocument, {
+                input: {
+                    lines: [{ orderLineId: order.lines[0].id, quantity: 2 }],
+                    handler: {
+                        code: manualFulfillmentHandler.code,
+                        arguments: [
+                            { name: 'method', value: 'Test2' },
+                            { name: 'trackingCode', value: '222' },
+                        ],
+                    },
+                },
+            });
+            fulfillmentGuard.assertSuccess(addFulfillmentToOrder);
+
+            const { transitionFulfillmentToState } = await adminClient.query(TransitFulfillmentDocument, {
+                id: addFulfillmentToOrder.id,
+                state: 'Shipped',
+            });
+            fulfillmentGuard.assertSuccess(transitionFulfillmentToState);
+
+            expect(transitionFulfillmentToState.id).toBe(addFulfillmentToOrder.id);
+            expect(transitionFulfillmentToState.state).toBe('Shipped');
+
+            const { order: order2 } = await adminClient.query(GetOrderDocument, {
+                id: order.id,
+            });
+            expect(order2?.state).toBe('PartiallyShipped');
+        });
     });
 });
 

+ 30 - 5
packages/core/src/service/helpers/utils/order-utils.ts

@@ -44,7 +44,10 @@ export function totalCoveredByPayments(order: Order, state?: PaymentState | Paym
  * Returns true if all (non-cancelled) OrderItems are delivered.
  */
 export function orderItemsAreDelivered(order: Order) {
-    return getOrderLinesFulfillmentStates(order).every(state => state === 'Delivered');
+    return (
+        getOrderLinesFulfillmentStates(order).every(state => state === 'Delivered') &&
+        !isOrderPartiallyFulfilled(order)
+    );
 }
 
 /**
@@ -52,7 +55,10 @@ export function orderItemsAreDelivered(order: Order) {
  */
 export function orderItemsArePartiallyDelivered(order: Order) {
     const states = getOrderLinesFulfillmentStates(order);
-    return states.some(state => state === 'Delivered') && !states.every(state => state === 'Delivered');
+    return (
+        states.some(state => state === 'Delivered') &&
+        (!states.every(state => state === 'Delivered') || isOrderPartiallyFulfilled(order))
+    );
 }
 
 function getOrderLinesFulfillmentStates(order: Order): Array<FulfillmentState | undefined> {
@@ -65,7 +71,7 @@ function getOrderLinesFulfillmentStates(order: Order): Array<FulfillmentState |
                     idsAreEqual(fl.orderLineId, line.id),
                 );
                 const totalFulfilled = summate(matchingFulfillmentLines, 'quantity');
-                if (totalFulfilled === line.quantity) {
+                if (0 < totalFulfilled) {
                     return matchingFulfillmentLines.map(l => l.fulfillment.state);
                 } else {
                     return undefined;
@@ -81,14 +87,20 @@ function getOrderLinesFulfillmentStates(order: Order): Array<FulfillmentState |
  */
 export function orderItemsArePartiallyShipped(order: Order) {
     const states = getOrderLinesFulfillmentStates(order);
-    return states.some(state => state === 'Shipped') && !states.every(state => state === 'Shipped');
+    return (
+        states.some(state => state === 'Shipped') &&
+        (!states.every(state => state === 'Shipped') || isOrderPartiallyFulfilled(order))
+    );
 }
 
 /**
  * Returns true if all (non-cancelled) OrderItems are shipped.
  */
 export function orderItemsAreShipped(order: Order) {
-    return getOrderLinesFulfillmentStates(order).every(state => state === 'Shipped');
+    return (
+        getOrderLinesFulfillmentStates(order).every(state => state === 'Shipped') &&
+        !isOrderPartiallyFulfilled(order)
+    );
 }
 
 /**
@@ -107,6 +119,19 @@ function getOrderFulfillmentLines(order: Order): FulfillmentLine[] {
         );
 }
 
+/**
+ * Returns true if Fulfillments exist for only some but not all of the
+ * order items.
+ */
+function isOrderPartiallyFulfilled(order: Order) {
+    const fulfillmentLines = getOrderFulfillmentLines(order);
+    const lines = fulfillmentLines.reduce((acc, item) => {
+        acc[item.orderLineId] = (acc[item.orderLineId] || 0) + item.quantity;
+        return acc;
+    }, {} as { [orderLineId: string]: number });
+    return order.lines.some(line => line.quantity > lines[line.id]);
+}
+
 export async function getOrdersFromLines(
     ctx: RequestContext,
     connection: TransactionalConnection,