Skip to content

Commit

Permalink
fix(core): Fix logic relating to partial fulfillments
Browse files Browse the repository at this point in the history
Fixes #2324 fixes #2191
  • Loading branch information
michaelbromley committed Aug 7, 2023
1 parent cf20ade commit 6f48ee2
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 5 deletions.
47 changes: 47 additions & 0 deletions packages/core/e2e/order.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import * as Codegen from './graphql/generated-e2e-admin-types';
import {
AddManualPaymentDocument,
CanceledOrderFragment,
CreateFulfillmentDocument,
ErrorCode,
FulfillmentFragment,
GetOrderDocument,
Expand All @@ -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 {
Expand Down Expand Up @@ -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');
});
});
});

Expand Down
35 changes: 30 additions & 5 deletions packages/core/src/service/helpers/utils/order-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,21 @@ 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)
);
}

/**
* Returns true if at least one, but not all (non-cancelled) OrderItems are delivered.
*/
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> {
Expand All @@ -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;
Expand All @@ -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)
);
}

/**
Expand All @@ -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,
Expand Down

0 comments on commit 6f48ee2

Please sign in to comment.