Skip to content

Commit

Permalink
feat(core): Create StockMovements when variant stock changed
Browse files Browse the repository at this point in the history
Relates to #81
  • Loading branch information
michaelbromley committed May 2, 2019
1 parent bf4185b commit f8521db
Show file tree
Hide file tree
Showing 19 changed files with 491 additions and 61 deletions.
28 changes: 21 additions & 7 deletions packages/common/src/generated-shop-types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// tslint:disable
// Generated in 2019-05-02T11:33:31+02:00
// Generated in 2019-05-02T12:27:28+02:00
export type Maybe<T> = T | null;

export interface OrderListOptions {
Expand Down Expand Up @@ -807,6 +807,20 @@ export interface PaginatedList {
totalItems: number;
}

export interface StockMovement {
id: string;

createdAt: DateTime;

updatedAt: DateTime;

productVariant: ProductVariant;

type: StockMovementType;

quantity: number;
}

// ====================================================
// Types
// ====================================================
Expand Down Expand Up @@ -1682,7 +1696,7 @@ export interface AssetList extends PaginatedList {
totalItems: number;
}

export interface Cancellation extends Node {
export interface Cancellation extends Node, StockMovement {
id: string;

createdAt: DateTime;
Expand Down Expand Up @@ -1786,7 +1800,7 @@ export interface PromotionList extends PaginatedList {
totalItems: number;
}

export interface Return extends Node {
export interface Return extends Node, StockMovement {
id: string;

createdAt: DateTime;
Expand All @@ -1808,7 +1822,7 @@ export interface RoleList extends PaginatedList {
totalItems: number;
}

export interface Sale extends Node {
export interface Sale extends Node, StockMovement {
id: string;

createdAt: DateTime;
Expand Down Expand Up @@ -1838,7 +1852,7 @@ export interface ShippingMethodList extends PaginatedList {
totalItems: number;
}

export interface StockAdjustment extends Node {
export interface StockAdjustment extends Node, StockMovement {
id: string;

createdAt: DateTime;
Expand All @@ -1853,7 +1867,7 @@ export interface StockAdjustment extends Node {
}

export interface StockMovementList {
items: StockMovement[];
items: StockMovementItem[];

totalItems: number;
}
Expand Down Expand Up @@ -1990,4 +2004,4 @@ export interface ResetPasswordMutationArgs {
/** The price of a search result product, either as a range or as a single price */
export type SearchResultPrice = PriceRange | SinglePrice;

export type StockMovement = StockAdjustment | Sale | Cancellation | Return;
export type StockMovementItem = StockAdjustment | Sale | Cancellation | Return;
30 changes: 23 additions & 7 deletions packages/common/src/generated-types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// tslint:disable
// Generated in 2019-05-02T11:33:32+02:00
// Generated in 2019-05-02T12:27:29+02:00
export type Maybe<T> = T | null;


Expand Down Expand Up @@ -4575,6 +4575,22 @@ export interface Node {
}


export interface StockMovement {

id: string;

createdAt: DateTime;

updatedAt: DateTime;

productVariant: ProductVariant;

type: StockMovementType;

quantity: number;
}




// ====================================================
Expand Down Expand Up @@ -5169,13 +5185,13 @@ export interface ProductVariantTranslation {

export interface StockMovementList {

items: StockMovement[];
items: StockMovementItem[];

totalItems: number;
}


export interface StockAdjustment extends Node {
export interface StockAdjustment extends Node,StockMovement {

id: string;

Expand All @@ -5191,7 +5207,7 @@ export interface StockAdjustment extends Node {
}


export interface Sale extends Node {
export interface Sale extends Node,StockMovement {

id: string;

Expand Down Expand Up @@ -5443,7 +5459,7 @@ export interface ShippingMethod extends Node {
}


export interface Cancellation extends Node {
export interface Cancellation extends Node,StockMovement {

id: string;

Expand All @@ -5461,7 +5477,7 @@ export interface Cancellation extends Node {
}


export interface Return extends Node {
export interface Return extends Node,StockMovement {

id: string;

Expand Down Expand Up @@ -6418,7 +6434,7 @@ export interface SetUiLanguageMutationArgs {



export type StockMovement = StockAdjustment | Sale | Cancellation | Return;
export type StockMovementItem = StockAdjustment | Sale | Cancellation | Return;

/** The price of a search result product, either as a range or as a single price */
export type SearchResultPrice = PriceRange | SinglePrice;
Expand Down
146 changes: 146 additions & 0 deletions packages/core/e2e/stock-control.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import gql from 'graphql-tag';
import path from 'path';

import { ProductVariant, StockMovementType, UpdateProductVariantInput } from '../../common/src/generated-types';

import { TEST_SETUP_TIMEOUT_MS } from './config/test-config';
import { TestAdminClient, TestShopClient } from './test-client';
import { TestServer } from './test-server';
import { assertThrowsWithMessage } from './utils/assert-throws-with-message';

jest.setTimeout(2137 * 1000);

describe('Stock control', () => {
const adminClient = new TestAdminClient();
const shopClient = new TestShopClient();
const server = new TestServer();

beforeAll(async () => {
const token = await server.init(
{
productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
customerCount: 2,
},
);
await shopClient.init();
await adminClient.init();
}, TEST_SETUP_TIMEOUT_MS);

afterAll(async () => {
await server.destroy();
});

describe('stock adjustments', () => {

let variants: ProductVariant[];

it('stockMovements are initially empty', async () => {
const result = await adminClient.query(GET_STOCK_MOVEMENT, { id: 'T_1' });

variants = result.product.variants;
for (const variant of variants) {
expect(variant.stockMovements.items).toEqual([]);
expect(variant.stockMovements.totalItems).toEqual(0);
}
});

it('updating ProductVariant with same stockOnHand does not create a StockMovement', async () => {
const result = await adminClient.query(UPDATE_STOCK_ON_HAND, {
input: [
{
id: variants[0].id,
stockOnHand: variants[0].stockOnHand,
},
] as UpdateProductVariantInput[],
});

expect(result.updateProductVariants[0].stockMovements.items).toEqual([]);
expect(result.updateProductVariants[0].stockMovements.totalItems).toEqual(0);
});

it('increasing stockOnHand creates a StockMovement with correct quantity', async () => {
const result = await adminClient.query(UPDATE_STOCK_ON_HAND, {
input: [
{
id: variants[0].id,
stockOnHand: variants[0].stockOnHand + 5,
},
] as UpdateProductVariantInput[],
});

expect(result.updateProductVariants[0].stockOnHand).toBe(5);
expect(result.updateProductVariants[0].stockMovements.totalItems).toEqual(1);
expect(result.updateProductVariants[0].stockMovements.items[0].type).toBe(StockMovementType.ADJUSTMENT);
expect(result.updateProductVariants[0].stockMovements.items[0].quantity).toBe(5);
});

it('decreasing stockOnHand creates a StockMovement with correct quantity', async () => {
const result = await adminClient.query(UPDATE_STOCK_ON_HAND, {
input: [
{
id: variants[0].id,
stockOnHand: variants[0].stockOnHand + 5 - 2,
},
] as UpdateProductVariantInput[],
});

expect(result.updateProductVariants[0].stockOnHand).toBe(3);
expect(result.updateProductVariants[0].stockMovements.totalItems).toEqual(2);
expect(result.updateProductVariants[0].stockMovements.items[1].type).toBe(StockMovementType.ADJUSTMENT);
expect(result.updateProductVariants[0].stockMovements.items[1].quantity).toBe(-2);
});

it('attempting to set a negative stockOnHand throws', assertThrowsWithMessage(
async () => {
const result = await adminClient.query(UPDATE_STOCK_ON_HAND, {
input: [
{
id: variants[0].id,
stockOnHand: -1,
},
] as UpdateProductVariantInput[],
});
},
'stockOnHand cannot be a negative value'),
);
});

});

const VARIANT_WITH_STOCK_FRAGMENT = gql`
fragment VariantWithStock on ProductVariant {
id
stockOnHand
stockMovements {
items {
...on StockMovement {
id
type
quantity
}
}
totalItems
}
}
`;

const GET_STOCK_MOVEMENT = gql`
query ($id: ID!) {
product(id: $id) {
id
variants {
...VariantWithStock
}
}
}
${VARIANT_WITH_STOCK_FRAGMENT}
`;

const UPDATE_STOCK_ON_HAND = gql`
mutation ($input: [UpdateProductVariantInput!]!) {
updateProductVariants(input: $input) {
...VariantWithStock
}
}
${VARIANT_WITH_STOCK_FRAGMENT}
`;
8 changes: 6 additions & 2 deletions packages/core/src/api/api-internal-modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import { OrderEntityResolver } from './resolvers/entity/order-entity.resolver';
import { OrderLineEntityResolver } from './resolvers/entity/order-line-entity.resolver';
import { ProductEntityResolver } from './resolvers/entity/product-entity.resolver';
import { ProductOptionGroupEntityResolver } from './resolvers/entity/product-option-group-entity.resolver';
import { ProductVariantEntityResolver } from './resolvers/entity/product-variant-entity.resolver';
import { ProductVariantAdminEntityResolver, ProductVariantEntityResolver } from './resolvers/entity/product-variant-entity.resolver';
import { ShopAuthResolver } from './resolvers/shop/shop-auth.resolver';
import { ShopCustomerResolver } from './resolvers/shop/shop-customer.resolver';
import { ShopEnvironmentResolver } from './resolvers/shop/shop-environment.resolver';
Expand Down Expand Up @@ -84,6 +84,10 @@ export const entityResolvers = [
ProductVariantEntityResolver,
];

export const adminEntityResolvers = [
ProductVariantAdminEntityResolver,
];

/**
* The internal module containing some shared providers used by more than
* one API module.
Expand All @@ -100,7 +104,7 @@ export class ApiSharedModule {}
*/
@Module({
imports: [ApiSharedModule, PluginModule, ServiceModule, DataImportModule],
providers: [...adminResolvers, ...entityResolvers, ...PluginModule.adminApiResolvers()],
providers: [...adminResolvers, ...entityResolvers, ...adminEntityResolvers, ...PluginModule.adminApiResolvers()],
exports: adminResolvers,
})
export class AdminApiModule {}
Expand Down
31 changes: 17 additions & 14 deletions packages/core/src/api/config/configure-graphql-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,21 @@ async function createGraphQLOptions(
},
};

const stockMovementResolveType = {
__resolveType(value: any) {
switch (value.type) {
case StockMovementType.ADJUSTMENT:
return 'StockAdjustment';
case StockMovementType.SALE:
return 'Sale';
case StockMovementType.CANCELLATION:
return 'Cancellation';
case StockMovementType.RETURN:
return 'Return';
}
},
};

return {
path: '/' + options.apiPath,
typeDefs: await createTypeDefs(options.apiType),
Expand All @@ -83,20 +98,8 @@ async function createGraphQLOptions(
return value.hasOwnProperty('value') ? 'SinglePrice' : 'PriceRange';
},
},
StockMovement: {
__resolveType(value: any) {
switch (value.type) {
case StockMovementType.ADJUSTMENT:
return 'StockAdjustment';
case StockMovementType.SALE:
return 'Sale';
case StockMovementType.CANCELLATION:
return 'Cancellation';
case StockMovementType.RETURN:
return 'Return';
}
},
},
StockMovementItem: stockMovementResolveType,
StockMovement: stockMovementResolveType,
},
uploads: {
maxFileSize: configService.assetOptions.uploadMaxFileSize,
Expand Down
Loading

0 comments on commit f8521db

Please sign in to comment.