|
|
@@ -4,7 +4,6 @@ import {
|
|
|
manualFulfillmentHandler,
|
|
|
mergeConfig,
|
|
|
Order,
|
|
|
- OrderPlacedStrategy,
|
|
|
OrderState,
|
|
|
RequestContext,
|
|
|
} from '@vendure/core';
|
|
|
@@ -997,51 +996,51 @@ describe('Stock control', () => {
|
|
|
describe('adjusting stockOnHand with negative outOfStockThreshold', () => {
|
|
|
const variant1Id = 'T_1';
|
|
|
beforeAll(async () => {
|
|
|
- await adminClient.query<UpdateProductVariants.Mutation, UpdateProductVariants.Variables>(
|
|
|
- UPDATE_PRODUCT_VARIANTS,
|
|
|
- {
|
|
|
- input: [
|
|
|
- {
|
|
|
- id: variant1Id,
|
|
|
- stockOnHand: 0,
|
|
|
- outOfStockThreshold: -20,
|
|
|
- trackInventory: GlobalFlag.TRUE,
|
|
|
- useGlobalOutOfStockThreshold: false,
|
|
|
- },
|
|
|
- ],
|
|
|
- },
|
|
|
- );
|
|
|
+ await adminClient.query<
|
|
|
+ Codegen.UpdateProductVariantsMutation,
|
|
|
+ Codegen.UpdateProductVariantsMutationVariables
|
|
|
+ >(UPDATE_PRODUCT_VARIANTS, {
|
|
|
+ input: [
|
|
|
+ {
|
|
|
+ id: variant1Id,
|
|
|
+ stockOnHand: 0,
|
|
|
+ outOfStockThreshold: -20,
|
|
|
+ trackInventory: GlobalFlag.TRUE,
|
|
|
+ useGlobalOutOfStockThreshold: false,
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ });
|
|
|
});
|
|
|
|
|
|
it(
|
|
|
'attempting to set stockOnHand below outOfStockThreshold throws',
|
|
|
assertThrowsWithMessage(async () => {
|
|
|
- const result = await adminClient.query<UpdateStock.Mutation, UpdateStock.Variables>(
|
|
|
- UPDATE_STOCK_ON_HAND,
|
|
|
- {
|
|
|
- input: [
|
|
|
- {
|
|
|
- id: variant1Id,
|
|
|
- stockOnHand: -21,
|
|
|
- },
|
|
|
- ] as UpdateProductVariantInput[],
|
|
|
- },
|
|
|
- );
|
|
|
- }, 'stockOnHand cannot be a negative value'),
|
|
|
- );
|
|
|
-
|
|
|
- it('can set negative stockOnHand that is not less than outOfStockThreshold', async () => {
|
|
|
- const result = await adminClient.query<UpdateStock.Mutation, UpdateStock.Variables>(
|
|
|
- UPDATE_STOCK_ON_HAND,
|
|
|
- {
|
|
|
+ const result = await adminClient.query<
|
|
|
+ Codegen.UpdateStockMutation,
|
|
|
+ Codegen.UpdateStockMutationVariables
|
|
|
+ >(UPDATE_STOCK_ON_HAND, {
|
|
|
input: [
|
|
|
{
|
|
|
id: variant1Id,
|
|
|
- stockOnHand: -10,
|
|
|
+ stockOnHand: -21,
|
|
|
},
|
|
|
] as UpdateProductVariantInput[],
|
|
|
- },
|
|
|
- );
|
|
|
+ });
|
|
|
+ }, 'stockOnHand cannot be a negative value'),
|
|
|
+ );
|
|
|
+
|
|
|
+ it('can set negative stockOnHand that is not less than outOfStockThreshold', async () => {
|
|
|
+ const result = await adminClient.query<
|
|
|
+ Codegen.UpdateStockMutation,
|
|
|
+ Codegen.UpdateStockMutationVariables
|
|
|
+ >(UPDATE_STOCK_ON_HAND, {
|
|
|
+ input: [
|
|
|
+ {
|
|
|
+ id: variant1Id,
|
|
|
+ stockOnHand: -10,
|
|
|
+ },
|
|
|
+ ] as UpdateProductVariantInput[],
|
|
|
+ });
|
|
|
expect(result.updateProductVariants[0]!.stockOnHand).toBe(-10);
|
|
|
});
|
|
|
});
|
|
|
@@ -1235,8 +1234,8 @@ describe('Stock control', () => {
|
|
|
|
|
|
await shopClient.asUserWithCredentials('trevor_donnelly96@hotmail.com', 'test');
|
|
|
const { addItemToOrder: add1 } = await shopClient.query<
|
|
|
- AddItemToOrder.Mutation,
|
|
|
- AddItemToOrder.Variables
|
|
|
+ CodegenShop.AddItemToOrderMutation,
|
|
|
+ CodegenShop.AddItemToOrderMutationVariables
|
|
|
>(ADD_ITEM_TO_ORDER, {
|
|
|
productVariantId: variant6.id,
|
|
|
quantity: 1,
|
|
|
@@ -1249,19 +1248,19 @@ describe('Stock control', () => {
|
|
|
input: { customFields: { test1557: true } },
|
|
|
});
|
|
|
|
|
|
- await shopClient.query<SetShippingAddress.Mutation, SetShippingAddress.Variables>(
|
|
|
- SET_SHIPPING_ADDRESS,
|
|
|
- {
|
|
|
- input: {
|
|
|
- streetLine1: '1 Test Street',
|
|
|
- countryCode: 'GB',
|
|
|
- } as CreateAddressInput,
|
|
|
- },
|
|
|
- );
|
|
|
+ await shopClient.query<
|
|
|
+ CodegenShop.SetShippingAddressMutation,
|
|
|
+ CodegenShop.SetShippingAddressMutationVariables
|
|
|
+ >(SET_SHIPPING_ADDRESS, {
|
|
|
+ input: {
|
|
|
+ streetLine1: '1 Test Street',
|
|
|
+ countryCode: 'GB',
|
|
|
+ } as CreateAddressInput,
|
|
|
+ });
|
|
|
await setFirstEligibleShippingMethod();
|
|
|
const { transitionOrderToState } = await shopClient.query<
|
|
|
- TransitionToState.Mutation,
|
|
|
- TransitionToState.Variables
|
|
|
+ CodegenShop.TransitionToStateMutation,
|
|
|
+ CodegenShop.TransitionToStateMutationVariables
|
|
|
>(TRANSITION_TO_STATE, { state: 'ArrangingPayment' });
|
|
|
orderGuard.assertSuccess(transitionOrderToState);
|
|
|
expect(transitionOrderToState.state).toBe('ArrangingPayment');
|
|
|
@@ -1272,19 +1271,19 @@ describe('Stock control', () => {
|
|
|
expect(variant6_2.stockOnHand).toBe(3);
|
|
|
expect(variant6_2.stockAllocated).toBe(0);
|
|
|
|
|
|
- const { cancelOrder } = await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(
|
|
|
- CANCEL_ORDER,
|
|
|
- {
|
|
|
- input: {
|
|
|
- orderId: transitionOrderToState.id,
|
|
|
- lines: transitionOrderToState.lines.map(l => ({
|
|
|
- orderLineId: l.id,
|
|
|
- quantity: l.quantity,
|
|
|
- })),
|
|
|
- reason: 'Cancelled by test',
|
|
|
- },
|
|
|
+ const { cancelOrder } = await adminClient.query<
|
|
|
+ Codegen.CancelOrderMutation,
|
|
|
+ Codegen.CancelOrderMutationVariables
|
|
|
+ >(CANCEL_ORDER, {
|
|
|
+ input: {
|
|
|
+ orderId: transitionOrderToState.id,
|
|
|
+ lines: transitionOrderToState.lines.map(l => ({
|
|
|
+ orderLineId: l.id,
|
|
|
+ quantity: l.quantity,
|
|
|
+ })),
|
|
|
+ reason: 'Cancelled by test',
|
|
|
},
|
|
|
- );
|
|
|
+ });
|
|
|
orderGuard.assertSuccess(cancelOrder);
|
|
|
|
|
|
const product3 = await getProductWithStockMovement('T_2');
|
|
|
@@ -1425,6 +1424,98 @@ describe('Stock control', () => {
|
|
|
expect(variant2_2.stockOnHand).toBe(1);
|
|
|
});
|
|
|
});
|
|
|
+
|
|
|
+ // https://github.com/vendure-ecommerce/vendure/issues/1738
|
|
|
+ describe('going out of stock after being added to order', () => {
|
|
|
+ const variantId = 'T_1';
|
|
|
+
|
|
|
+ beforeAll(async () => {
|
|
|
+ const { updateProductVariants } = await adminClient.query<
|
|
|
+ Codegen.UpdateStockMutation,
|
|
|
+ Codegen.UpdateStockMutationVariables
|
|
|
+ >(UPDATE_STOCK_ON_HAND, {
|
|
|
+ input: [
|
|
|
+ {
|
|
|
+ id: variantId,
|
|
|
+ stockOnHand: 1,
|
|
|
+ trackInventory: GlobalFlag.TRUE,
|
|
|
+ useGlobalOutOfStockThreshold: false,
|
|
|
+ outOfStockThreshold: 0,
|
|
|
+ },
|
|
|
+ ] as UpdateProductVariantInput[],
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ it('prevents checkout if no saleable stock', async () => {
|
|
|
+ // First customer adds to order
|
|
|
+ await shopClient.asUserWithCredentials('hayden.zieme12@hotmail.com', 'test');
|
|
|
+ const { addItemToOrder: add1 } = await shopClient.query<
|
|
|
+ CodegenShop.AddItemToOrderMutation,
|
|
|
+ CodegenShop.AddItemToOrderMutationVariables
|
|
|
+ >(ADD_ITEM_TO_ORDER, {
|
|
|
+ productVariantId: variantId,
|
|
|
+ quantity: 1,
|
|
|
+ });
|
|
|
+ orderGuard.assertSuccess(add1);
|
|
|
+
|
|
|
+ // Second customer adds to order
|
|
|
+ await shopClient.asUserWithCredentials('marques.sawayn@hotmail.com', 'test');
|
|
|
+ const { addItemToOrder: add2 } = await shopClient.query<
|
|
|
+ CodegenShop.AddItemToOrderMutation,
|
|
|
+ CodegenShop.AddItemToOrderMutationVariables
|
|
|
+ >(ADD_ITEM_TO_ORDER, {
|
|
|
+ productVariantId: variantId,
|
|
|
+ quantity: 1,
|
|
|
+ });
|
|
|
+ orderGuard.assertSuccess(add2);
|
|
|
+
|
|
|
+ // first customer can check out
|
|
|
+ await shopClient.asUserWithCredentials('hayden.zieme12@hotmail.com', 'test');
|
|
|
+ await proceedToArrangingPayment(shopClient);
|
|
|
+ const result1 = await addPaymentToOrder(shopClient, testSuccessfulPaymentMethod);
|
|
|
+ orderGuard.assertSuccess(result1);
|
|
|
+
|
|
|
+ const product1 = await getProductWithStockMovement('T_1');
|
|
|
+ const variant = product1?.variants.find(v => v.id === variantId);
|
|
|
+ expect(variant!.stockOnHand).toBe(1);
|
|
|
+ expect(variant!.stockAllocated).toBe(1);
|
|
|
+
|
|
|
+ // second customer CANNOT check out
|
|
|
+ await shopClient.asUserWithCredentials('marques.sawayn@hotmail.com', 'test');
|
|
|
+ await shopClient.query<
|
|
|
+ CodegenShop.SetShippingAddressMutation,
|
|
|
+ CodegenShop.SetShippingAddressMutationVariables
|
|
|
+ >(SET_SHIPPING_ADDRESS, {
|
|
|
+ input: {
|
|
|
+ fullName: 'name',
|
|
|
+ streetLine1: '12 the street',
|
|
|
+ city: 'foo',
|
|
|
+ postalCode: '123456',
|
|
|
+ countryCode: 'US',
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ const { eligibleShippingMethods } = await shopClient.query<CodegenShop.GetShippingMethodsQuery>(
|
|
|
+ GET_ELIGIBLE_SHIPPING_METHODS,
|
|
|
+ );
|
|
|
+ const { setOrderShippingMethod } = await shopClient.query<
|
|
|
+ CodegenShop.SetShippingMethodMutation,
|
|
|
+ CodegenShop.SetShippingMethodMutationVariables
|
|
|
+ >(SET_SHIPPING_METHOD, {
|
|
|
+ id: eligibleShippingMethods[1].id,
|
|
|
+ });
|
|
|
+ orderGuard.assertSuccess(setOrderShippingMethod);
|
|
|
+ const { transitionOrderToState } = await shopClient.query<
|
|
|
+ CodegenShop.TransitionToStateMutation,
|
|
|
+ CodegenShop.TransitionToStateMutationVariables
|
|
|
+ >(TRANSITION_TO_STATE, { state: 'ArrangingPayment' });
|
|
|
+ orderGuard.assertErrorResult(transitionOrderToState);
|
|
|
+
|
|
|
+ expect(transitionOrderToState!.transitionError).toBe(
|
|
|
+ 'Cannot transition Order to the "ArrangingPayment" state due to insufficient stock of Laptop 13 inch 8GB',
|
|
|
+ );
|
|
|
+ });
|
|
|
+ });
|
|
|
});
|
|
|
|
|
|
const UPDATE_STOCK_ON_HAND = gql`
|