From 1380ae2c6e65aab0dc04947c7ee904414e4ef7c0 Mon Sep 17 00:00:00 2001 From: Kasper Date: Wed, 28 Sep 2022 18:21:31 +0200 Subject: [PATCH 1/6] add packages --- .../medusa-js/src/resources/admin/variants.ts | 2 +- .../src/api/routes/admin/variants/index.ts | 5 +- .../routes/admin/variants/list-variants.ts | 163 +++++++++++++++--- .../routes/store/variants/list-variants.ts | 12 +- packages/medusa/src/types/price-selection.ts | 6 + 5 files changed, 158 insertions(+), 30 deletions(-) diff --git a/packages/medusa-js/src/resources/admin/variants.ts b/packages/medusa-js/src/resources/admin/variants.ts index df504420d88c2..5a6e153cd6e51 100644 --- a/packages/medusa-js/src/resources/admin/variants.ts +++ b/packages/medusa-js/src/resources/admin/variants.ts @@ -1,4 +1,4 @@ -import { AdminVariantsListRes, AdminGetVariantsParams } from "@medusajs/medusa" +import { AdminGetVariantsParams, AdminVariantsListRes } from "@medusajs/medusa" import qs from "qs" import { ResponsePromise } from "../.." import BaseResource from "../base" diff --git a/packages/medusa/src/api/routes/admin/variants/index.ts b/packages/medusa/src/api/routes/admin/variants/index.ts index 53df40b7719a3..ba4edb1609c78 100644 --- a/packages/medusa/src/api/routes/admin/variants/index.ts +++ b/packages/medusa/src/api/routes/admin/variants/index.ts @@ -1,7 +1,8 @@ import { Router } from "express" -import { PaginatedResponse } from "../../../../types/common" import { ProductVariant } from "../../../../models/product-variant" +import { PaginatedResponse } from "../../../../types/common" +import { PricedVariant } from "../../../../types/pricing" import middlewares from "../../../middlewares" const route = Router() @@ -69,7 +70,7 @@ export const allowedAdminVariantRelations: (keyof ProductVariant)[] = [ ] export type AdminVariantsListRes = PaginatedResponse & { - variants: ProductVariant[] + variants: PricedVariant[] } export * from "./list-variants" diff --git a/packages/medusa/src/api/routes/admin/variants/list-variants.ts b/packages/medusa/src/api/routes/admin/variants/list-variants.ts index dcbd18d98b213..23ee7708b01a3 100644 --- a/packages/medusa/src/api/routes/admin/variants/list-variants.ts +++ b/packages/medusa/src/api/routes/admin/variants/list-variants.ts @@ -1,12 +1,23 @@ import { IsInt, IsOptional, IsString } from "class-validator" import { defaultAdminVariantFields, defaultAdminVariantRelations } from "./" -import { FilterableProductVariantProps } from "../../../../types/product-variant" -import { FindConfig } from "../../../../types/common" +import { Type } from "class-transformer" +import { omit } from "lodash" import { ProductVariant } from "../../../../models/product-variant" +import { + CartService, + PricingService, + RegionService, +} from "../../../../services" import ProductVariantService from "../../../../services/product-variant" -import { Type } from "class-transformer" +import { + FindConfig, + NumericalComparisonOperator, +} from "../../../../types/common" +import { AdminPriceSelectionParams } from "../../../../types/price-selection" +import { FilterableProductVariantProps } from "../../../../types/product-variant" import { validator } from "../../../../utils/validator" +import { IsType } from "../../../../utils/validators/is-type" /** * @oas [get] /variants @@ -15,9 +26,51 @@ import { validator } from "../../../../utils/validator" * description: "Retrieves a list of Product Variants" * x-authenticated: true * parameters: - * - (query) q {string} Query used for searching variants. - * - (query) offset=0 {integer} How many variants to skip in the result. - * - (query) limit=20 {integer} Limit the number of variants returned. + * - (query) id {string} A Product Variant id to filter by. + * - (query) ids {string} A comma separated list of Product Variant ids to filter by. + * - (query) expand {string} A comma separated list of Product Variant relations to load. + * - (query) fields {string} A comma separated list of Product Variant fields to include. + * - (query) offset=0 {number} How many product variants to skip in the result. + * - (query) limit=100 {number} Maximum number of Product Variants to return. + * - (query) cart_id {string} The id of the cart to use for price selection. + * - (query) region_id {string} The id of the region to use for price selection. + * - (query) currency_code {string} The currency code to use for price selection. + * - (query) customer_id {string} The id of the customer to use for price selection. + * - in: query + * name: title + * style: form + * explode: false + * description: product variant title to search for. + * schema: + * oneOf: + * - type: string + * description: a single title to search by + * - type: array + * description: multiple titles to search by + * items: + * type: string + * - in: query + * name: inventory_quantity + * description: Filter by available inventory quantity + * schema: + * oneOf: + * - type: number + * description: a specific number to search by. + * - type: object + * description: search using less and greater than comparisons. + * properties: + * lt: + * type: number + * description: filter by inventory quantity less than this number + * gt: + * type: number + * description: filter by inventory quantity greater than this number + * lte: + * type: number + * description: filter by inventory quantity less than or equal to this number + * gte: + * type: number + * description: filter by inventory quantity greater than or equal to this number * x-codeSamples: * - lang: JavaScript * label: JS Client @@ -76,45 +129,113 @@ export default async (req, res) => { const variantService: ProductVariantService = req.scope.resolve( "productVariantService" ) + const pricingService: PricingService = req.scope.resolve("pricingService") + const cartService: CartService = req.scope.resolve("cartService") + const regionService: RegionService = req.scope.resolve("regionService") - const { offset, limit, q } = await validator( - AdminGetVariantsParams, - req.query - ) + const validated = await validator(AdminGetVariantsParams, req.query) + + const { expand, offset, limit } = validated - const selector: FilterableProductVariantProps = {} + let expandFields: string[] = [] + if (expand) { + expandFields = expand.split(",") + } - if ("q" in req.query) { - selector.q = q + let includeFields: (keyof ProductVariant)[] = [] + if (validated.fields) { + const set = new Set(validated.fields.split(",")) as Set< + keyof ProductVariant + > + set.add("id") + includeFields = [...set] } + const filterableFields: FilterableProductVariantProps = omit(validated, [ + "ids", + "limit", + "offset", + "expand", + "cart_id", + "region_id", + "currency_code", + "customer_id", + ]) + const listConfig: FindConfig = { - select: defaultAdminVariantFields, - relations: defaultAdminVariantRelations, + select: includeFields.length ? includeFields : defaultAdminVariantFields, + relations: expandFields.length + ? expandFields + : defaultAdminVariantRelations, skip: offset, take: limit, } - const [variants, count] = await variantService.listAndCount( - selector, + const [rawVariants, count] = await variantService.listAndCount( + filterableFields, listConfig ) + let regionId = validated.region_id + let currencyCode = validated.currency_code + if (validated.cart_id) { + const cart = await cartService.retrieve(validated.cart_id, { + select: ["id", "region_id"], + }) + const region = await regionService.retrieve(cart.region_id, { + select: ["id", "currency_code"], + }) + regionId = region.id + currencyCode = region.currency_code + } + + const variants = await pricingService.setVariantPrices(rawVariants, { + cart_id: validated.cart_id, + region_id: regionId, + currency_code: currencyCode, + customer_id: validated.customer_id, + include_discount_prices: true, + }) + res.json({ variants, count, offset, limit }) } -export class AdminGetVariantsParams { - @IsString() +export class AdminGetVariantsParams extends AdminPriceSelectionParams { @IsOptional() + @IsString() q?: string - @IsInt() @IsOptional() + @IsInt() @Type(() => Number) limit?: number = 20 - @IsInt() @IsOptional() + @IsInt() @Type(() => Number) offset?: number = 0 + + @IsOptional() + @IsString() + expand?: string + + @IsString() + @IsOptional() + fields?: string + + @IsOptional() + @IsString() + ids?: string + + @IsOptional() + @IsType([String, [String]]) + id?: string | string[] + + @IsOptional() + @IsType([String, [String]]) + title?: string | string[] + + @IsOptional() + @IsType([Number, NumericalComparisonOperator]) + inventory_quantity?: number | NumericalComparisonOperator } diff --git a/packages/medusa/src/api/routes/store/variants/list-variants.ts b/packages/medusa/src/api/routes/store/variants/list-variants.ts index 1899f822670ce..d1909804a6419 100644 --- a/packages/medusa/src/api/routes/store/variants/list-variants.ts +++ b/packages/medusa/src/api/routes/store/variants/list-variants.ts @@ -1,19 +1,19 @@ +import { IsInt, IsOptional, IsString } from "class-validator" import { CartService, PricingService, ProductVariantService, RegionService, } from "../../../../services" -import { IsInt, IsOptional, IsString } from "class-validator" -import { FilterableProductVariantProps } from "../../../../types/product-variant" -import { IsType } from "../../../../utils/validators/is-type" -import { NumericalComparisonOperator } from "../../../../types/common" -import { PriceSelectionParams } from "../../../../types/price-selection" import { Type } from "class-transformer" -import { defaultStoreVariantRelations } from "." import { omit } from "lodash" +import { defaultStoreVariantRelations } from "." +import { NumericalComparisonOperator } from "../../../../types/common" +import { PriceSelectionParams } from "../../../../types/price-selection" +import { FilterableProductVariantProps } from "../../../../types/product-variant" import { validator } from "../../../../utils/validator" +import { IsType } from "../../../../utils/validators/is-type" /** * @oas [get] /variants diff --git a/packages/medusa/src/types/price-selection.ts b/packages/medusa/src/types/price-selection.ts index 0ba7dd99c25db..53c8c4a159e78 100644 --- a/packages/medusa/src/types/price-selection.ts +++ b/packages/medusa/src/types/price-selection.ts @@ -13,3 +13,9 @@ export class PriceSelectionParams { @IsString() currency_code?: string } + +export class AdminPriceSelectionParams extends PriceSelectionParams { + @IsOptional() + @IsString() + customer_id?: string +} From c9b1291b473acb6c73a6ff18b8fa025068bcad6d Mon Sep 17 00:00:00 2001 From: Kasper Date: Wed, 28 Sep 2022 18:43:53 +0200 Subject: [PATCH 2/6] add integration test --- .../admin/__snapshots__/variant.js.snap | 97 ++++++++++++++ .../api/__tests__/admin/variant.js | 122 ++++++++++++++++++ integration-tests/api/package.json | 6 +- integration-tests/api/yarn.lock | 74 +++++------ 4 files changed, 259 insertions(+), 40 deletions(-) create mode 100644 integration-tests/api/__tests__/admin/__snapshots__/variant.js.snap diff --git a/integration-tests/api/__tests__/admin/__snapshots__/variant.js.snap b/integration-tests/api/__tests__/admin/__snapshots__/variant.js.snap new file mode 100644 index 0000000000000..66c9d83d5ea3e --- /dev/null +++ b/integration-tests/api/__tests__/admin/__snapshots__/variant.js.snap @@ -0,0 +1,97 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`/admin/products GET /admin/variants price selection strategy selects prices based on the passed currency code 1`] = ` +Object { + "count": 1, + "limit": 20, + "offset": 0, + "variants": Array [ + Object { + "allow_backorder": false, + "barcode": "test-barcode", + "calculated_price": 80, + "calculated_price_incl_tax": null, + "calculated_price_type": "sale", + "calculated_tax": null, + "created_at": Any, + "deleted_at": null, + "ean": "test-ean", + "height": null, + "hs_code": null, + "id": "test-variant", + "inventory_quantity": 10, + "length": null, + "manage_inventory": true, + "material": null, + "metadata": null, + "mid_code": null, + "options": Array [ + Object { + "created_at": Any, + "deleted_at": null, + "id": "test-variant-option", + "metadata": null, + "option_id": "test-option", + "updated_at": Any, + "value": "Default variant", + "variant_id": "test-variant", + }, + ], + "origin_country": null, + "original_price": 100, + "original_price_incl_tax": null, + "original_tax": null, + "prices": Array [ + Object { + "amount": 100, + "created_at": Any, + "currency_code": "usd", + "deleted_at": null, + "id": "test-price", + "max_quantity": null, + "min_quantity": null, + "price_list": null, + "price_list_id": null, + "region_id": null, + "updated_at": Any, + "variant_id": "test-variant", + }, + Object { + "amount": 80, + "created_at": Any, + "currency_code": "usd", + "deleted_at": null, + "id": "test-price-discount", + "max_quantity": null, + "min_quantity": null, + "price_list": Object { + "created_at": Any, + "deleted_at": null, + "description": "Winter sale for VIP customers.", + "ends_at": null, + "id": "pl", + "name": "VIP winter sale", + "starts_at": null, + "status": "active", + "type": "sale", + "updated_at": Any, + }, + "price_list_id": "pl", + "region_id": null, + "updated_at": Any, + "variant_id": "test-variant", + }, + ], + "product": Any, + "product_id": "test-product", + "sku": "test-sku", + "tax_rates": null, + "title": "Test variant", + "upc": "test-upc", + "updated_at": Any, + "weight": null, + "width": null, + }, + ], +} +`; diff --git a/integration-tests/api/__tests__/admin/variant.js b/integration-tests/api/__tests__/admin/variant.js index 3f8934d830b42..cf1499b667eaf 100644 --- a/integration-tests/api/__tests__/admin/variant.js +++ b/integration-tests/api/__tests__/admin/variant.js @@ -3,9 +3,11 @@ const path = require("path") const setupServer = require("../../../helpers/setup-server") const { useApi } = require("../../../helpers/use-api") const { initDb, useDb } = require("../../../helpers/use-db") +const { simpleProductFactory } = require("../../factories") const adminSeeder = require("../../helpers/admin-seeder") const productSeeder = require("../../helpers/product-seeder") +const storeProductSeeder = require("../../helpers/store-product-seeder") jest.setTimeout(30000) @@ -145,4 +147,124 @@ describe("/admin/products", () => { ) }) }) + + describe("GET /admin/variants price selection strategy", () => { + beforeEach(async () => { + await storeProductSeeder(dbConnection) + await adminSeeder(dbConnection) + + await simpleProductFactory( + dbConnection, + { + title: "prod", + variants: [ + { + title: "test1", + inventory_quantity: 10, + }, + { + title: "test2", + inventory_quantity: 12, + }, + ], + }, + 100 + ) + }) + + afterEach(async () => { + const db = useDb() + await db.teardown() + }) + + it("selects prices based on the passed currency code", async () => { + const api = useApi() + + const response = await api.get( + "/admin/variants?id=test-variant¤cy_code=usd", + { + headers: { + Authorization: "Bearer test_token", + }, + } + ) + + console.warn("response", response.data.variants) + + expect(response.data).toMatchSnapshot({ + variants: [ + { + allow_backorder: false, + barcode: "test-barcode", + created_at: expect.any(String), + deleted_at: null, + ean: "test-ean", + height: null, + hs_code: null, + id: "test-variant", + inventory_quantity: 10, + length: null, + manage_inventory: true, + material: null, + metadata: null, + mid_code: null, + origin_country: null, + product_id: "test-product", + sku: "test-sku", + title: "Test variant", + upc: "test-upc", + updated_at: expect.any(String), + weight: null, + width: null, + options: [ + { + created_at: expect.any(String), + updated_at: expect.any(String), + }, + ], + prices: [ + { + created_at: expect.any(String), + updated_at: expect.any(String), + amount: 100, + currency_code: "usd", + deleted_at: null, + id: "test-price", + region_id: null, + min_quantity: null, + max_quantity: null, + price_list_id: null, + variant_id: "test-variant", + }, + { + created_at: expect.any(String), + updated_at: expect.any(String), + amount: 80, + currency_code: "usd", + deleted_at: null, + price_list_id: "pl", + id: "test-price-discount", + region_id: null, + variant_id: "test-variant", + price_list: { + created_at: expect.any(String), + updated_at: expect.any(String), + id: "pl", + }, + }, + ], + product: expect.any(Object), + original_price: 100, + calculated_price: 80, + calculated_price_type: "sale", + original_price_incl_tax: null, + calculated_price_incl_tax: null, + original_tax: null, + calculated_tax: null, + tax_rates: null, + }, + ], + }) + }) + }) }) diff --git a/integration-tests/api/package.json b/integration-tests/api/package.json index 6580acaf885df..ba9c650554dd9 100644 --- a/integration-tests/api/package.json +++ b/integration-tests/api/package.json @@ -8,16 +8,16 @@ "build": "babel src -d dist --extensions \".ts,.js\"" }, "dependencies": { - "@medusajs/medusa": "1.3.7-dev-1662369149992", + "@medusajs/medusa": "1.4.1-dev-1664382371714", "faker": "^5.5.3", - "medusa-interfaces": "1.3.3-dev-1662369149992", + "medusa-interfaces": "1.3.3-dev-1664382371714", "typeorm": "^0.2.31" }, "devDependencies": { "@babel/cli": "^7.12.10", "@babel/core": "^7.12.10", "@babel/node": "^7.12.10", - "babel-preset-medusa-package": "1.1.19-dev-1662369149992", + "babel-preset-medusa-package": "1.1.19-dev-1664382371714", "jest": "^26.6.3" } } diff --git a/integration-tests/api/yarn.lock b/integration-tests/api/yarn.lock index 1ab81d8bb5ce0..2209a938421f1 100644 --- a/integration-tests/api/yarn.lock +++ b/integration-tests/api/yarn.lock @@ -1775,9 +1775,9 @@ __metadata: languageName: node linkType: hard -"@medusajs/medusa-cli@npm:1.3.2-dev-1662369149992": - version: 1.3.2-dev-1662369149992 - resolution: "@medusajs/medusa-cli@npm:1.3.2-dev-1662369149992" +"@medusajs/medusa-cli@npm:1.3.3-dev-1664382371714": + version: 1.3.3-dev-1664382371714 + resolution: "@medusajs/medusa-cli@npm:1.3.3-dev-1664382371714" dependencies: "@babel/polyfill": ^7.8.7 "@babel/runtime": ^7.9.6 @@ -1793,8 +1793,8 @@ __metadata: inquirer: ^8.0.0 is-valid-path: ^0.1.1 meant: ^1.0.1 - medusa-core-utils: 1.1.31-dev-1662369149992 - medusa-telemetry: 0.0.13-dev-1662369149992 + medusa-core-utils: 1.1.31-dev-1664382371714 + medusa-telemetry: 0.0.13-dev-1664382371714 netrc-parser: ^3.1.6 open: ^8.0.6 ora: ^5.4.1 @@ -1809,15 +1809,15 @@ __metadata: yargs: ^15.3.1 bin: medusa: cli.js - checksum: b7b163bcbba3ef8d8e4ce35cff8b1e90f764916d88a23eef54475dc616f04997ce423cb402684437dd8ef2594a5f6d5a51a3a30129f18d189b71a857ff429a95 + checksum: 9b027258b6affe00e6360e30d3d91247cbbd93487cebd12649b173276beda7449f09f8172d0f7772eb1ed16d6783dfab0ab3f4096005ec945ad914fd8cdf02f1 languageName: node linkType: hard -"@medusajs/medusa@npm:1.3.7-dev-1662369149992": - version: 1.3.7-dev-1662369149992 - resolution: "@medusajs/medusa@npm:1.3.7-dev-1662369149992" +"@medusajs/medusa@npm:1.4.1-dev-1664382371714": + version: 1.4.1-dev-1664382371714 + resolution: "@medusajs/medusa@npm:1.4.1-dev-1664382371714" dependencies: - "@medusajs/medusa-cli": 1.3.2-dev-1662369149992 + "@medusajs/medusa-cli": 1.3.3-dev-1664382371714 "@types/ioredis": ^4.28.10 "@types/lodash": ^4.14.168 awilix: ^4.2.3 @@ -1839,8 +1839,8 @@ __metadata: ioredis-mock: ^5.6.0 iso8601-duration: ^1.3.0 jsonwebtoken: ^8.5.1 - medusa-core-utils: 1.1.31-dev-1662369149992 - medusa-test-utils: 1.1.37-dev-1662369149992 + medusa-core-utils: 1.1.31-dev-1664382371714 + medusa-test-utils: 1.1.37-dev-1664382371714 morgan: ^1.9.1 multer: ^1.4.2 node-schedule: ^2.1.0 @@ -1865,7 +1865,7 @@ __metadata: typeorm: 0.2.x bin: medusa: cli.js - checksum: 8335278bd5019c94919ca73df4b29f8c47daab24fca0a5d4671eda5c2bbddb45a5dd31986f4a3a865e418c2e1c164680a046645a5fc33f49d2132c4a14b42aac + checksum: 2b359d58abd4ea1313882e9c04ba54434563a45d80a98f211f8cceea62d10a4ad7a2566d50ddf4a97a0cd4465b679b87af764baa483884699fbad1fc4f34f466 languageName: node linkType: hard @@ -2446,11 +2446,11 @@ __metadata: "@babel/cli": ^7.12.10 "@babel/core": ^7.12.10 "@babel/node": ^7.12.10 - "@medusajs/medusa": 1.3.7-dev-1662369149992 - babel-preset-medusa-package: 1.1.19-dev-1662369149992 + "@medusajs/medusa": 1.4.1-dev-1664382371714 + babel-preset-medusa-package: 1.1.19-dev-1664382371714 faker: ^5.5.3 jest: ^26.6.3 - medusa-interfaces: 1.3.3-dev-1662369149992 + medusa-interfaces: 1.3.3-dev-1664382371714 typeorm: ^0.2.31 languageName: unknown linkType: soft @@ -2757,9 +2757,9 @@ __metadata: languageName: node linkType: hard -"babel-preset-medusa-package@npm:1.1.19-dev-1662369149992": - version: 1.1.19-dev-1662369149992 - resolution: "babel-preset-medusa-package@npm:1.1.19-dev-1662369149992" +"babel-preset-medusa-package@npm:1.1.19-dev-1664382371714": + version: 1.1.19-dev-1664382371714 + resolution: "babel-preset-medusa-package@npm:1.1.19-dev-1664382371714" dependencies: "@babel/plugin-proposal-class-properties": ^7.12.1 "@babel/plugin-proposal-decorators": ^7.12.1 @@ -2773,7 +2773,7 @@ __metadata: core-js: ^3.7.0 peerDependencies: "@babel/core": ^7.11.6 - checksum: 934e46bb0b231cc328eeccf40c9caa47ab48bc021af3355836f4b142690540ae24935b3d45154ac5665517d831b882455cab134733027000900ddd7cef4f2eb1 + checksum: 5e8881b3617e6c962a9b2e97c0484c3f0994a77ba6a17ee866134569b95c58fb6eaacfc86bd138675317b956e39e3c21ea8c063a56eb87c9bb70b960fc92ff9e languageName: node linkType: hard @@ -6906,29 +6906,29 @@ __metadata: languageName: node linkType: hard -"medusa-core-utils@npm:1.1.31-dev-1662369149992": - version: 1.1.31-dev-1662369149992 - resolution: "medusa-core-utils@npm:1.1.31-dev-1662369149992" +"medusa-core-utils@npm:1.1.31-dev-1664382371714": + version: 1.1.31-dev-1664382371714 + resolution: "medusa-core-utils@npm:1.1.31-dev-1664382371714" dependencies: joi: ^17.3.0 joi-objectid: ^3.0.1 - checksum: 1a0574f3b4833b1be47cb6ff947c1547ab610eaedaa15710eac2929f5ce6d80948bd92f7a594e199dc85f1d9a6d4ce8c58350ae155632d83991196c13cdbc9cf + checksum: 5641a2abc76eb72ab8e6e37414ca5f9add45742016fb8ef096f64a2a6002bdde67cd59e44958d8878659eee03f8eb3fd02122b5d30276220376ff4797430ea00 languageName: node linkType: hard -"medusa-interfaces@npm:1.3.3-dev-1662369149992": - version: 1.3.3-dev-1662369149992 - resolution: "medusa-interfaces@npm:1.3.3-dev-1662369149992" +"medusa-interfaces@npm:1.3.3-dev-1664382371714": + version: 1.3.3-dev-1664382371714 + resolution: "medusa-interfaces@npm:1.3.3-dev-1664382371714" peerDependencies: medusa-core-utils: ^1.1.31 typeorm: 0.x - checksum: eaddeda76f24bc9c2469973eeb0e5bcbfbafa2a0f10505bf79d8e0f6cf12e14ffd7300f03035194457c768f15e16988365e9fccbb4ded170e825253984b3cf0e + checksum: 87fbf2848de46084d9e19c6a8c50ed575e907d1660d69dfff070f33e5050b205ac16363982e2769a465b12d7d73963263fa796fab251028e559bf1e1a35edce0 languageName: node linkType: hard -"medusa-telemetry@npm:0.0.13-dev-1662369149992": - version: 0.0.13-dev-1662369149992 - resolution: "medusa-telemetry@npm:0.0.13-dev-1662369149992" +"medusa-telemetry@npm:0.0.13-dev-1664382371714": + version: 0.0.13-dev-1664382371714 + resolution: "medusa-telemetry@npm:0.0.13-dev-1664382371714" dependencies: axios: ^0.21.1 axios-retry: ^3.1.9 @@ -6939,18 +6939,18 @@ __metadata: is-docker: ^2.2.1 remove-trailing-slash: ^0.1.1 uuid: ^8.3.2 - checksum: 76a4c1e417b05baa5f8d0bacfa78cb50f3fece1490722d08d751b7269ae715a6b6b668dbc9d580b3e43e484d13d57e03fbbfe7a696eab355fb39b0a46f87f5ec + checksum: e277428cdc420b7eccda96788751a3dc3dfc3deece4749154ec46c302fd741dfcfb58a25dab6550c4b5c32014ef90c422ab015c6725a2456e8a9526afb9685ce languageName: node linkType: hard -"medusa-test-utils@npm:1.1.37-dev-1662369149992": - version: 1.1.37-dev-1662369149992 - resolution: "medusa-test-utils@npm:1.1.37-dev-1662369149992" +"medusa-test-utils@npm:1.1.37-dev-1664382371714": + version: 1.1.37-dev-1664382371714 + resolution: "medusa-test-utils@npm:1.1.37-dev-1664382371714" dependencies: "@babel/plugin-transform-classes": ^7.9.5 - medusa-core-utils: 1.1.31-dev-1662369149992 + medusa-core-utils: 1.1.31-dev-1664382371714 randomatic: ^3.1.1 - checksum: f8dab2548bdae681141fce55be9d1a770d62a237a682fc0df02ce6d03bf27e908d82058f1e1d7ced1aa5a9a66c28381ec9c8b63c75100921b128b9808594e33d + checksum: 2e17dcac8b8535762e83ad5c8d1abe4751fca38b59642762494842fd049943fc40afe702cbc75cbb721b13007610caf8ac2f66d5bc7d3e096eda0653313696c2 languageName: node linkType: hard From 0184f8974051a0d39e1ae64c2d9376fa3982dc7c Mon Sep 17 00:00:00 2001 From: Kasper Date: Wed, 28 Sep 2022 18:48:56 +0200 Subject: [PATCH 3/6] add changeset --- .changeset/few-maps-hammer.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/few-maps-hammer.md diff --git a/.changeset/few-maps-hammer.md b/.changeset/few-maps-hammer.md new file mode 100644 index 0000000000000..eecd0eb0adb18 --- /dev/null +++ b/.changeset/few-maps-hammer.md @@ -0,0 +1,6 @@ +--- +"@medusajs/medusa": patch +"@medusajs/medusa-js": patch +--- + +Adds the use of price selection strategy to retrieving variants in the admin API. This moves the responsibility of tax calculations from the frontend (admin) to the backend. From 0b2a734601f7a7038639d4776ebae92d686f6a21 Mon Sep 17 00:00:00 2001 From: Kasper Date: Mon, 3 Oct 2022 17:17:14 +0200 Subject: [PATCH 4/6] add more integration tests --- .../admin/__snapshots__/variant.js.snap | 155 +++++++++++------- .../api/__tests__/admin/variant.js | 139 +++++++++------- .../api/helpers/store-product-seeder.js | 123 ++++++++++++++ integration-tests/api/package.json | 6 +- integration-tests/api/yarn.lock | 74 ++++----- 5 files changed, 339 insertions(+), 158 deletions(-) diff --git a/integration-tests/api/__tests__/admin/__snapshots__/variant.js.snap b/integration-tests/api/__tests__/admin/__snapshots__/variant.js.snap index 66c9d83d5ea3e..a75f262246814 100644 --- a/integration-tests/api/__tests__/admin/__snapshots__/variant.js.snap +++ b/integration-tests/api/__tests__/admin/__snapshots__/variant.js.snap @@ -3,8 +3,6 @@ exports[`/admin/products GET /admin/variants price selection strategy selects prices based on the passed currency code 1`] = ` Object { "count": 1, - "limit": 20, - "offset": 0, "variants": Array [ Object { "allow_backorder": false, @@ -25,63 +23,12 @@ Object { "material": null, "metadata": null, "mid_code": null, - "options": Array [ - Object { - "created_at": Any, - "deleted_at": null, - "id": "test-variant-option", - "metadata": null, - "option_id": "test-option", - "updated_at": Any, - "value": "Default variant", - "variant_id": "test-variant", - }, - ], + "options": Any, "origin_country": null, "original_price": 100, "original_price_incl_tax": null, "original_tax": null, - "prices": Array [ - Object { - "amount": 100, - "created_at": Any, - "currency_code": "usd", - "deleted_at": null, - "id": "test-price", - "max_quantity": null, - "min_quantity": null, - "price_list": null, - "price_list_id": null, - "region_id": null, - "updated_at": Any, - "variant_id": "test-variant", - }, - Object { - "amount": 80, - "created_at": Any, - "currency_code": "usd", - "deleted_at": null, - "id": "test-price-discount", - "max_quantity": null, - "min_quantity": null, - "price_list": Object { - "created_at": Any, - "deleted_at": null, - "description": "Winter sale for VIP customers.", - "ends_at": null, - "id": "pl", - "name": "VIP winter sale", - "starts_at": null, - "status": "active", - "type": "sale", - "updated_at": Any, - }, - "price_list_id": "pl", - "region_id": null, - "updated_at": Any, - "variant_id": "test-variant", - }, - ], + "prices": Any, "product": Any, "product_id": "test-product", "sku": "test-sku", @@ -95,3 +42,101 @@ Object { ], } `; + +exports[`/admin/products GET /admin/variants price selection strategy selects prices based on the passed region id 1`] = ` +Object { + "count": 1, + "variants": Array [ + Object { + "allow_backorder": false, + "barcode": "test-barcode_multi-reg", + "calculated_price": 80, + "calculated_price_incl_tax": 80, + "calculated_price_type": "sale", + "calculated_tax": 0, + "created_at": Any, + "deleted_at": null, + "ean": "test-ean-multi-reg", + "height": null, + "hs_code": null, + "id": "test-variant-multi-reg", + "inventory_quantity": 10, + "length": null, + "manage_inventory": true, + "material": null, + "metadata": null, + "mid_code": null, + "options": Any, + "origin_country": null, + "original_price": 100, + "original_price_incl_tax": 100, + "original_tax": 0, + "prices": Any, + "product": Any, + "product_id": "test-product-multi-reg", + "sku": "test-sku-multi-reg", + "tax_rates": Array [ + Object { + "code": "default", + "name": "default", + "rate": 0, + }, + ], + "title": "Test variant", + "upc": "test-upc-multi-reg", + "updated_at": Any, + "weight": null, + "width": null, + }, + ], +} +`; + +exports[`/admin/products GET /admin/variants price selection strategy selects prices based on the passed region id and customer id 1`] = ` +Object { + "count": 1, + "variants": Array [ + Object { + "allow_backorder": false, + "barcode": "test-barcode_multi-reg", + "calculated_price": 40, + "calculated_price_incl_tax": 40, + "calculated_price_type": "sale", + "calculated_tax": 0, + "created_at": Any, + "deleted_at": null, + "ean": "test-ean-multi-reg", + "height": null, + "hs_code": null, + "id": "test-variant-multi-reg", + "inventory_quantity": 10, + "length": null, + "manage_inventory": true, + "material": null, + "metadata": null, + "mid_code": null, + "options": Any, + "origin_country": null, + "original_price": 100, + "original_price_incl_tax": 100, + "original_tax": 0, + "prices": Any, + "product": Any, + "product_id": "test-product-multi-reg", + "sku": "test-sku-multi-reg", + "tax_rates": Array [ + Object { + "code": "default", + "name": "default", + "rate": 0, + }, + ], + "title": "Test variant", + "upc": "test-upc-multi-reg", + "updated_at": Any, + "weight": null, + "width": null, + }, + ], +} +`; diff --git a/integration-tests/api/__tests__/admin/variant.js b/integration-tests/api/__tests__/admin/variant.js index cf1499b667eaf..5093f579ecbd4 100644 --- a/integration-tests/api/__tests__/admin/variant.js +++ b/integration-tests/api/__tests__/admin/variant.js @@ -150,7 +150,11 @@ describe("/admin/products", () => { describe("GET /admin/variants price selection strategy", () => { beforeEach(async () => { - await storeProductSeeder(dbConnection) + try { + await storeProductSeeder(dbConnection) + } catch (err) { + console.log(err) + } await adminSeeder(dbConnection) await simpleProductFactory( @@ -189,71 +193,10 @@ describe("/admin/products", () => { } ) - console.warn("response", response.data.variants) - expect(response.data).toMatchSnapshot({ variants: [ { - allow_backorder: false, - barcode: "test-barcode", - created_at: expect.any(String), - deleted_at: null, - ean: "test-ean", - height: null, - hs_code: null, id: "test-variant", - inventory_quantity: 10, - length: null, - manage_inventory: true, - material: null, - metadata: null, - mid_code: null, - origin_country: null, - product_id: "test-product", - sku: "test-sku", - title: "Test variant", - upc: "test-upc", - updated_at: expect.any(String), - weight: null, - width: null, - options: [ - { - created_at: expect.any(String), - updated_at: expect.any(String), - }, - ], - prices: [ - { - created_at: expect.any(String), - updated_at: expect.any(String), - amount: 100, - currency_code: "usd", - deleted_at: null, - id: "test-price", - region_id: null, - min_quantity: null, - max_quantity: null, - price_list_id: null, - variant_id: "test-variant", - }, - { - created_at: expect.any(String), - updated_at: expect.any(String), - amount: 80, - currency_code: "usd", - deleted_at: null, - price_list_id: "pl", - id: "test-price-discount", - region_id: null, - variant_id: "test-variant", - price_list: { - created_at: expect.any(String), - updated_at: expect.any(String), - id: "pl", - }, - }, - ], - product: expect.any(Object), original_price: 100, calculated_price: 80, calculated_price_type: "sale", @@ -261,7 +204,77 @@ describe("/admin/products", () => { calculated_price_incl_tax: null, original_tax: null, calculated_tax: null, - tax_rates: null, + options: expect.any(Array), + prices: expect.any(Array), + product: expect.any(Object), + created_at: expect.any(String), + updated_at: expect.any(String), + }, + ], + }) + }) + + it("selects prices based on the passed region id", async () => { + const api = useApi() + + const response = await api.get( + "/admin/variants?id=test-variant-multi-reg®ion_id=reg-europe", + { + headers: { + Authorization: "Bearer test_token", + }, + } + ) + + expect(response.data).toMatchSnapshot({ + variants: [ + { + id: "test-variant-multi-reg", + original_price: 100, + calculated_price: 80, + calculated_price_type: "sale", + original_price_incl_tax: 100, + calculated_price_incl_tax: 80, + original_tax: 0, + calculated_tax: 0, + options: expect.any(Array), + prices: expect.any(Array), + product: expect.any(Object), + created_at: expect.any(String), + updated_at: expect.any(String), + }, + ], + }) + }) + + it("selects prices based on the passed region id and customer id", async () => { + const api = useApi() + + const response = await api.get( + "/admin/variants?id=test-variant-multi-reg®ion_id=reg-europe&customer_id=test-customer", + { + headers: { + Authorization: "Bearer test_token", + }, + } + ) + + expect(response.data).toMatchSnapshot({ + variants: [ + { + id: "test-variant-multi-reg", + original_price: 100, + calculated_price: 40, + calculated_price_type: "sale", + original_price_incl_tax: 100, + calculated_price_incl_tax: 40, + original_tax: 0, + calculated_tax: 0, + prices: expect.any(Array), + options: expect.any(Array), + product: expect.any(Object), + created_at: expect.any(String), + updated_at: expect.any(String), }, ], }) diff --git a/integration-tests/api/helpers/store-product-seeder.js b/integration-tests/api/helpers/store-product-seeder.js index 2290465c1a9f7..d653837f7fbd6 100644 --- a/integration-tests/api/helpers/store-product-seeder.js +++ b/integration-tests/api/helpers/store-product-seeder.js @@ -10,6 +10,8 @@ const { Image, Cart, PriceList, + CustomerGroup, + Customer, } = require("@medusajs/medusa") module.exports = async (connection, data = {}) => { @@ -369,4 +371,125 @@ module.exports = async (connection, data = {}) => { }) await manager.save(gift_card) + + await manager.insert(Region, { + id: "reg-europe", + name: "Test Region Europe", + currency_code: "eur", + tax_rate: 0, + }) + + const customer = await manager.create(Customer, { + id: "test-customer", + email: "john@doe.com", + first_name: "John", + last_name: "Doe", + password_hash: + "c2NyeXB0AAEAAAABAAAAAVMdaddoGjwU1TafDLLlBKnOTQga7P2dbrfgf3fB+rCD/cJOMuGzAvRdKutbYkVpuJWTU39P7OpuWNkUVoEETOVLMJafbI8qs8Qx/7jMQXkN", // password matching "test" + has_account: true, + }) + + const customerGroup = await manager.create(CustomerGroup, { + id: "test-group", + name: "test-group", + }) + + await manager.save(customerGroup) + customer.groups = [customerGroup] + await manager.save(customer) + + const priceListWithCustomers = await manager.create(PriceList, { + id: "pl_with_customers", + name: "VIP winter sale", + description: "Winter sale for VIP customers.", + type: "sale", + status: "active", + }) + + priceListWithCustomers.customer_groups = [customerGroup] + + await manager.save(priceListWithCustomers) + + const productMultiReg = manager.create(Product, { + id: "test-product-multi-reg", + handle: "test-product-reg", + title: "Multi Reg Test product", + profile_id: defaultProfile.id, + description: "test-product-description", + status: "published", + collection_id: "test-collection", + type: { id: "test-type", value: "test-type" }, + tags: [ + { id: "tag1", value: "123" }, + { tag: "tag2", value: "456" }, + ], + }) + + productMultiReg.images = [image] + + await manager.save(productMultiReg) + + const variantMultiReg = await manager.create(ProductVariant, { + id: "test-variant-multi-reg", + inventory_quantity: 10, + title: "Test variant", + variant_rank: 0, + sku: "test-sku-multi-reg", + ean: "test-ean-multi-reg", + upc: "test-upc-multi-reg", + barcode: "test-barcode_multi-reg", + product_id: "test-product-multi-reg", + prices: [ + { + id: "test-price-multi-usd", + currency_code: "usd", + type: "default", + amount: 100, + }, + { + id: "test-price-discount-multi-usd", + currency_code: "usd", + amount: 80, + price_list_id: "pl", + }, + { + id: "test-price-discount-expired-multi-usd", + currency_code: "usd", + amount: 70, + price_list_id: "pl_expired", + }, + { + id: "test-price-multi-eur", + currency_code: "eur", + amount: 100, + }, + { + id: "test-price-discount-multi-eur", + currency_code: "eur", + amount: 80, + price_list_id: "pl", + }, + { + id: "test-price-discount-multi-eur-with-customer", + currency_code: "eur", + amount: 40, + price_list_id: "pl_with_customers", + }, + { + id: "test-price-discount-expired-multi-eur", + currency_code: "eur", + amount: 70, + price_list_id: "pl_expired", + }, + ], + options: [ + { + id: "test-variant-option-reg", + value: "Default variant", + option_id: "test-option", + }, + ], + }) + + await manager.save(variantMultiReg) } diff --git a/integration-tests/api/package.json b/integration-tests/api/package.json index ba9c650554dd9..b1ad5a6818892 100644 --- a/integration-tests/api/package.json +++ b/integration-tests/api/package.json @@ -8,16 +8,16 @@ "build": "babel src -d dist --extensions \".ts,.js\"" }, "dependencies": { - "@medusajs/medusa": "1.4.1-dev-1664382371714", + "@medusajs/medusa": "1.4.1-dev-1664548572642", "faker": "^5.5.3", - "medusa-interfaces": "1.3.3-dev-1664382371714", + "medusa-interfaces": "1.3.3-dev-1664548572642", "typeorm": "^0.2.31" }, "devDependencies": { "@babel/cli": "^7.12.10", "@babel/core": "^7.12.10", "@babel/node": "^7.12.10", - "babel-preset-medusa-package": "1.1.19-dev-1664382371714", + "babel-preset-medusa-package": "1.1.19-dev-1664548572642", "jest": "^26.6.3" } } diff --git a/integration-tests/api/yarn.lock b/integration-tests/api/yarn.lock index 2209a938421f1..5cc0ca5d3cbb9 100644 --- a/integration-tests/api/yarn.lock +++ b/integration-tests/api/yarn.lock @@ -1775,9 +1775,9 @@ __metadata: languageName: node linkType: hard -"@medusajs/medusa-cli@npm:1.3.3-dev-1664382371714": - version: 1.3.3-dev-1664382371714 - resolution: "@medusajs/medusa-cli@npm:1.3.3-dev-1664382371714" +"@medusajs/medusa-cli@npm:1.3.3-dev-1664548572642": + version: 1.3.3-dev-1664548572642 + resolution: "@medusajs/medusa-cli@npm:1.3.3-dev-1664548572642" dependencies: "@babel/polyfill": ^7.8.7 "@babel/runtime": ^7.9.6 @@ -1793,8 +1793,8 @@ __metadata: inquirer: ^8.0.0 is-valid-path: ^0.1.1 meant: ^1.0.1 - medusa-core-utils: 1.1.31-dev-1664382371714 - medusa-telemetry: 0.0.13-dev-1664382371714 + medusa-core-utils: 1.1.31-dev-1664548572642 + medusa-telemetry: 0.0.13-dev-1664548572642 netrc-parser: ^3.1.6 open: ^8.0.6 ora: ^5.4.1 @@ -1809,15 +1809,15 @@ __metadata: yargs: ^15.3.1 bin: medusa: cli.js - checksum: 9b027258b6affe00e6360e30d3d91247cbbd93487cebd12649b173276beda7449f09f8172d0f7772eb1ed16d6783dfab0ab3f4096005ec945ad914fd8cdf02f1 + checksum: 73631f55740e272bf173184df0fe94b8106e6c53a85a06aa2c477227fa19ddf377c9b42e34683a39849e91836d29fd4fbe0192ad2ecc9994c1190994c836c6c1 languageName: node linkType: hard -"@medusajs/medusa@npm:1.4.1-dev-1664382371714": - version: 1.4.1-dev-1664382371714 - resolution: "@medusajs/medusa@npm:1.4.1-dev-1664382371714" +"@medusajs/medusa@npm:1.4.1-dev-1664548572642": + version: 1.4.1-dev-1664548572642 + resolution: "@medusajs/medusa@npm:1.4.1-dev-1664548572642" dependencies: - "@medusajs/medusa-cli": 1.3.3-dev-1664382371714 + "@medusajs/medusa-cli": 1.3.3-dev-1664548572642 "@types/ioredis": ^4.28.10 "@types/lodash": ^4.14.168 awilix: ^4.2.3 @@ -1839,8 +1839,8 @@ __metadata: ioredis-mock: ^5.6.0 iso8601-duration: ^1.3.0 jsonwebtoken: ^8.5.1 - medusa-core-utils: 1.1.31-dev-1664382371714 - medusa-test-utils: 1.1.37-dev-1664382371714 + medusa-core-utils: 1.1.31-dev-1664548572642 + medusa-test-utils: 1.1.37-dev-1664548572642 morgan: ^1.9.1 multer: ^1.4.2 node-schedule: ^2.1.0 @@ -1865,7 +1865,7 @@ __metadata: typeorm: 0.2.x bin: medusa: cli.js - checksum: 2b359d58abd4ea1313882e9c04ba54434563a45d80a98f211f8cceea62d10a4ad7a2566d50ddf4a97a0cd4465b679b87af764baa483884699fbad1fc4f34f466 + checksum: bd67281e7e7c45913074f45572731f9779d1ed1b999113ea67f6b4ea9216f3ea37df75b66d6e27d2bed1837434370efb3617af24da93571133003ae07b7d2f5e languageName: node linkType: hard @@ -2446,11 +2446,11 @@ __metadata: "@babel/cli": ^7.12.10 "@babel/core": ^7.12.10 "@babel/node": ^7.12.10 - "@medusajs/medusa": 1.4.1-dev-1664382371714 - babel-preset-medusa-package: 1.1.19-dev-1664382371714 + "@medusajs/medusa": 1.4.1-dev-1664548572642 + babel-preset-medusa-package: 1.1.19-dev-1664548572642 faker: ^5.5.3 jest: ^26.6.3 - medusa-interfaces: 1.3.3-dev-1664382371714 + medusa-interfaces: 1.3.3-dev-1664548572642 typeorm: ^0.2.31 languageName: unknown linkType: soft @@ -2757,9 +2757,9 @@ __metadata: languageName: node linkType: hard -"babel-preset-medusa-package@npm:1.1.19-dev-1664382371714": - version: 1.1.19-dev-1664382371714 - resolution: "babel-preset-medusa-package@npm:1.1.19-dev-1664382371714" +"babel-preset-medusa-package@npm:1.1.19-dev-1664548572642": + version: 1.1.19-dev-1664548572642 + resolution: "babel-preset-medusa-package@npm:1.1.19-dev-1664548572642" dependencies: "@babel/plugin-proposal-class-properties": ^7.12.1 "@babel/plugin-proposal-decorators": ^7.12.1 @@ -2773,7 +2773,7 @@ __metadata: core-js: ^3.7.0 peerDependencies: "@babel/core": ^7.11.6 - checksum: 5e8881b3617e6c962a9b2e97c0484c3f0994a77ba6a17ee866134569b95c58fb6eaacfc86bd138675317b956e39e3c21ea8c063a56eb87c9bb70b960fc92ff9e + checksum: 74f61921185e75fb0c80777208809f7b7e469108b66aefdcb8ba14e4419ac1582d5703c4408488fdbc5282e6bc7740491cc3f2830f964821ff59319f65de7d3a languageName: node linkType: hard @@ -6906,29 +6906,29 @@ __metadata: languageName: node linkType: hard -"medusa-core-utils@npm:1.1.31-dev-1664382371714": - version: 1.1.31-dev-1664382371714 - resolution: "medusa-core-utils@npm:1.1.31-dev-1664382371714" +"medusa-core-utils@npm:1.1.31-dev-1664548572642": + version: 1.1.31-dev-1664548572642 + resolution: "medusa-core-utils@npm:1.1.31-dev-1664548572642" dependencies: joi: ^17.3.0 joi-objectid: ^3.0.1 - checksum: 5641a2abc76eb72ab8e6e37414ca5f9add45742016fb8ef096f64a2a6002bdde67cd59e44958d8878659eee03f8eb3fd02122b5d30276220376ff4797430ea00 + checksum: f5f39d7eeffbf8c893d64f72d04e7a3f844718c4b9759094fbf213406e7fb12dc5ec6825a3ceec1d8c3bf462a5e3049ad0d6ddb93a7c7b530cd384b176e3bf8e languageName: node linkType: hard -"medusa-interfaces@npm:1.3.3-dev-1664382371714": - version: 1.3.3-dev-1664382371714 - resolution: "medusa-interfaces@npm:1.3.3-dev-1664382371714" +"medusa-interfaces@npm:1.3.3-dev-1664548572642": + version: 1.3.3-dev-1664548572642 + resolution: "medusa-interfaces@npm:1.3.3-dev-1664548572642" peerDependencies: medusa-core-utils: ^1.1.31 typeorm: 0.x - checksum: 87fbf2848de46084d9e19c6a8c50ed575e907d1660d69dfff070f33e5050b205ac16363982e2769a465b12d7d73963263fa796fab251028e559bf1e1a35edce0 + checksum: b358ce3d19b48f539569f5c69e60cb9927ac59bf2fabb9f24dab1d7ae8fa3a42fd5c4b127f37c119139b0063ee071e2b370d61749c5971a32af32f130713e700 languageName: node linkType: hard -"medusa-telemetry@npm:0.0.13-dev-1664382371714": - version: 0.0.13-dev-1664382371714 - resolution: "medusa-telemetry@npm:0.0.13-dev-1664382371714" +"medusa-telemetry@npm:0.0.13-dev-1664548572642": + version: 0.0.13-dev-1664548572642 + resolution: "medusa-telemetry@npm:0.0.13-dev-1664548572642" dependencies: axios: ^0.21.1 axios-retry: ^3.1.9 @@ -6939,18 +6939,18 @@ __metadata: is-docker: ^2.2.1 remove-trailing-slash: ^0.1.1 uuid: ^8.3.2 - checksum: e277428cdc420b7eccda96788751a3dc3dfc3deece4749154ec46c302fd741dfcfb58a25dab6550c4b5c32014ef90c422ab015c6725a2456e8a9526afb9685ce + checksum: 5be02967eb94e7db2883b6c22c1e213979d04bcd63a59c38ddc6f5711b97bc5fd7fd9e59833c6ecf56c936ab8847d7860bd429498670450ab48d7889d12d7919 languageName: node linkType: hard -"medusa-test-utils@npm:1.1.37-dev-1664382371714": - version: 1.1.37-dev-1664382371714 - resolution: "medusa-test-utils@npm:1.1.37-dev-1664382371714" +"medusa-test-utils@npm:1.1.37-dev-1664548572642": + version: 1.1.37-dev-1664548572642 + resolution: "medusa-test-utils@npm:1.1.37-dev-1664548572642" dependencies: "@babel/plugin-transform-classes": ^7.9.5 - medusa-core-utils: 1.1.31-dev-1664382371714 + medusa-core-utils: 1.1.31-dev-1664548572642 randomatic: ^3.1.1 - checksum: 2e17dcac8b8535762e83ad5c8d1abe4751fca38b59642762494842fd049943fc40afe702cbc75cbb721b13007610caf8ac2f66d5bc7d3e096eda0653313696c2 + checksum: c91853a098ec381c8d7768f8f450ea0b94f6b9a6f44bae87fa0820574c4adb9d1b6a628d32e901a6b041a5690ddaa93235a4875d526e3a68e3aee7ef434012d6 languageName: node linkType: hard From 0a4dac9885dbb1c58a1f49091134cf4c5d5a27a9 Mon Sep 17 00:00:00 2001 From: Kasper Date: Mon, 3 Oct 2022 17:18:34 +0200 Subject: [PATCH 5/6] add all updates --- .../src/api/routes/admin/variants/index.ts | 14 +++- .../routes/admin/variants/list-variants.ts | 71 +++++-------------- 2 files changed, 30 insertions(+), 55 deletions(-) diff --git a/packages/medusa/src/api/routes/admin/variants/index.ts b/packages/medusa/src/api/routes/admin/variants/index.ts index ba4edb1609c78..3e5cf1a7f2b78 100644 --- a/packages/medusa/src/api/routes/admin/variants/index.ts +++ b/packages/medusa/src/api/routes/admin/variants/index.ts @@ -3,14 +3,24 @@ import { Router } from "express" import { ProductVariant } from "../../../../models/product-variant" import { PaginatedResponse } from "../../../../types/common" import { PricedVariant } from "../../../../types/pricing" -import middlewares from "../../../middlewares" +import middlewares, { transformQuery } from "../../../middlewares" +import { AdminGetVariantsParams } from "./list-variants" const route = Router() export default (app) => { app.use("/variants", route) - route.get("/", middlewares.wrap(require("./list-variants").default)) + route.get( + "/", + transformQuery(AdminGetVariantsParams, { + defaultRelations: defaultAdminVariantRelations, + defaultFields: defaultAdminVariantFields, + allowedFields: allowedAdminVariantFields, + isList: true, + }), + middlewares.wrap(require("./list-variants").default) + ) return app } diff --git a/packages/medusa/src/api/routes/admin/variants/list-variants.ts b/packages/medusa/src/api/routes/admin/variants/list-variants.ts index 23ee7708b01a3..0cd323843e02c 100644 --- a/packages/medusa/src/api/routes/admin/variants/list-variants.ts +++ b/packages/medusa/src/api/routes/admin/variants/list-variants.ts @@ -1,22 +1,15 @@ import { IsInt, IsOptional, IsString } from "class-validator" -import { defaultAdminVariantFields, defaultAdminVariantRelations } from "./" import { Type } from "class-transformer" import { omit } from "lodash" -import { ProductVariant } from "../../../../models/product-variant" import { CartService, PricingService, RegionService, } from "../../../../services" import ProductVariantService from "../../../../services/product-variant" -import { - FindConfig, - NumericalComparisonOperator, -} from "../../../../types/common" +import { NumericalComparisonOperator } from "../../../../types/common" import { AdminPriceSelectionParams } from "../../../../types/price-selection" -import { FilterableProductVariantProps } from "../../../../types/product-variant" -import { validator } from "../../../../utils/validator" import { IsType } from "../../../../utils/validators/is-type" /** @@ -129,57 +122,28 @@ export default async (req, res) => { const variantService: ProductVariantService = req.scope.resolve( "productVariantService" ) + const pricingService: PricingService = req.scope.resolve("pricingService") const cartService: CartService = req.scope.resolve("cartService") const regionService: RegionService = req.scope.resolve("regionService") - const validated = await validator(AdminGetVariantsParams, req.query) - - const { expand, offset, limit } = validated - - let expandFields: string[] = [] - if (expand) { - expandFields = expand.split(",") - } - - let includeFields: (keyof ProductVariant)[] = [] - if (validated.fields) { - const set = new Set(validated.fields.split(",")) as Set< - keyof ProductVariant - > - set.add("id") - includeFields = [...set] - } - - const filterableFields: FilterableProductVariantProps = omit(validated, [ - "ids", - "limit", - "offset", - "expand", + // We need to remove the price selection params from the array of fields + const cleanFilterableFields = omit(req.filterableFields, [ "cart_id", "region_id", "currency_code", "customer_id", ]) - const listConfig: FindConfig = { - select: includeFields.length ? includeFields : defaultAdminVariantFields, - relations: expandFields.length - ? expandFields - : defaultAdminVariantRelations, - skip: offset, - take: limit, - } - const [rawVariants, count] = await variantService.listAndCount( - filterableFields, - listConfig + cleanFilterableFields, + req.listConfig ) - let regionId = validated.region_id - let currencyCode = validated.currency_code - if (validated.cart_id) { - const cart = await cartService.retrieve(validated.cart_id, { + let regionId = req.validatedQuery.region_id + let currencyCode = req.validatedQuery.currency_code + if (req.validatedQuery.cart_id) { + const cart = await cartService.retrieve(req.validatedQuery.cart_id, { select: ["id", "region_id"], }) const region = await regionService.retrieve(cart.region_id, { @@ -190,14 +154,19 @@ export default async (req, res) => { } const variants = await pricingService.setVariantPrices(rawVariants, { - cart_id: validated.cart_id, + cart_id: req.validatedQuery.cart_id, region_id: regionId, currency_code: currencyCode, - customer_id: validated.customer_id, + customer_id: req.validatedQuery.customer_id, include_discount_prices: true, }) - res.json({ variants, count, offset, limit }) + res.json({ + variants, + count, + offset: req.listConfig.offset, + limit: req.listConfig.limit, + }) } export class AdminGetVariantsParams extends AdminPriceSelectionParams { @@ -223,10 +192,6 @@ export class AdminGetVariantsParams extends AdminPriceSelectionParams { @IsOptional() fields?: string - @IsOptional() - @IsString() - ids?: string - @IsOptional() @IsType([String, [String]]) id?: string | string[] From cb545e84301a5bcea8d33a7942495017eb684664 Mon Sep 17 00:00:00 2001 From: Kasper Date: Tue, 4 Oct 2022 10:47:02 +0200 Subject: [PATCH 6/6] fix integration tests --- .../admin/__snapshots__/variant.js.snap | 24 +-- .../api/__tests__/admin/variant.js | 30 +-- .../api/helpers/admin-variants-seeder.js | 192 ++++++++++++++++++ .../api/helpers/store-product-seeder.js | 121 ----------- 4 files changed, 211 insertions(+), 156 deletions(-) create mode 100644 integration-tests/api/helpers/admin-variants-seeder.js diff --git a/integration-tests/api/__tests__/admin/__snapshots__/variant.js.snap b/integration-tests/api/__tests__/admin/__snapshots__/variant.js.snap index a75f262246814..6f2d966c25ea4 100644 --- a/integration-tests/api/__tests__/admin/__snapshots__/variant.js.snap +++ b/integration-tests/api/__tests__/admin/__snapshots__/variant.js.snap @@ -49,17 +49,17 @@ Object { "variants": Array [ Object { "allow_backorder": false, - "barcode": "test-barcode_multi-reg", + "barcode": "test-barcode", "calculated_price": 80, "calculated_price_incl_tax": 80, "calculated_price_type": "sale", "calculated_tax": 0, "created_at": Any, "deleted_at": null, - "ean": "test-ean-multi-reg", + "ean": "test-ean", "height": null, "hs_code": null, - "id": "test-variant-multi-reg", + "id": "test-variant", "inventory_quantity": 10, "length": null, "manage_inventory": true, @@ -73,8 +73,8 @@ Object { "original_tax": 0, "prices": Any, "product": Any, - "product_id": "test-product-multi-reg", - "sku": "test-sku-multi-reg", + "product_id": "test-product", + "sku": "test-sku", "tax_rates": Array [ Object { "code": "default", @@ -83,7 +83,7 @@ Object { }, ], "title": "Test variant", - "upc": "test-upc-multi-reg", + "upc": "test-upc", "updated_at": Any, "weight": null, "width": null, @@ -98,17 +98,17 @@ Object { "variants": Array [ Object { "allow_backorder": false, - "barcode": "test-barcode_multi-reg", + "barcode": "test-barcode", "calculated_price": 40, "calculated_price_incl_tax": 40, "calculated_price_type": "sale", "calculated_tax": 0, "created_at": Any, "deleted_at": null, - "ean": "test-ean-multi-reg", + "ean": "test-ean", "height": null, "hs_code": null, - "id": "test-variant-multi-reg", + "id": "test-variant", "inventory_quantity": 10, "length": null, "manage_inventory": true, @@ -122,8 +122,8 @@ Object { "original_tax": 0, "prices": Any, "product": Any, - "product_id": "test-product-multi-reg", - "sku": "test-sku-multi-reg", + "product_id": "test-product", + "sku": "test-sku", "tax_rates": Array [ Object { "code": "default", @@ -132,7 +132,7 @@ Object { }, ], "title": "Test variant", - "upc": "test-upc-multi-reg", + "upc": "test-upc", "updated_at": Any, "weight": null, "width": null, diff --git a/integration-tests/api/__tests__/admin/variant.js b/integration-tests/api/__tests__/admin/variant.js index 5093f579ecbd4..baa028936464f 100644 --- a/integration-tests/api/__tests__/admin/variant.js +++ b/integration-tests/api/__tests__/admin/variant.js @@ -6,6 +6,7 @@ const { initDb, useDb } = require("../../../helpers/use-db") const { simpleProductFactory } = require("../../factories") const adminSeeder = require("../../helpers/admin-seeder") +const adminVariantsSeeder = require("../../helpers/admin-variants-seeder") const productSeeder = require("../../helpers/product-seeder") const storeProductSeeder = require("../../helpers/store-product-seeder") @@ -119,6 +120,7 @@ describe("/admin/products", () => { it("lists all product variants matching a specific product title", async () => { const api = useApi() + const response = await api .get("/admin/variants?q=Test product1", { headers: { @@ -151,29 +153,11 @@ describe("/admin/products", () => { describe("GET /admin/variants price selection strategy", () => { beforeEach(async () => { try { - await storeProductSeeder(dbConnection) + await adminVariantsSeeder(dbConnection) } catch (err) { console.log(err) } await adminSeeder(dbConnection) - - await simpleProductFactory( - dbConnection, - { - title: "prod", - variants: [ - { - title: "test1", - inventory_quantity: 10, - }, - { - title: "test2", - inventory_quantity: 12, - }, - ], - }, - 100 - ) }) afterEach(async () => { @@ -218,7 +202,7 @@ describe("/admin/products", () => { const api = useApi() const response = await api.get( - "/admin/variants?id=test-variant-multi-reg®ion_id=reg-europe", + "/admin/variants?id=test-variant®ion_id=reg-europe", { headers: { Authorization: "Bearer test_token", @@ -229,7 +213,7 @@ describe("/admin/products", () => { expect(response.data).toMatchSnapshot({ variants: [ { - id: "test-variant-multi-reg", + id: "test-variant", original_price: 100, calculated_price: 80, calculated_price_type: "sale", @@ -251,7 +235,7 @@ describe("/admin/products", () => { const api = useApi() const response = await api.get( - "/admin/variants?id=test-variant-multi-reg®ion_id=reg-europe&customer_id=test-customer", + "/admin/variants?id=test-variant®ion_id=reg-europe&customer_id=test-customer", { headers: { Authorization: "Bearer test_token", @@ -262,7 +246,7 @@ describe("/admin/products", () => { expect(response.data).toMatchSnapshot({ variants: [ { - id: "test-variant-multi-reg", + id: "test-variant", original_price: 100, calculated_price: 40, calculated_price_type: "sale", diff --git a/integration-tests/api/helpers/admin-variants-seeder.js b/integration-tests/api/helpers/admin-variants-seeder.js new file mode 100644 index 0000000000000..9c8a82a63a952 --- /dev/null +++ b/integration-tests/api/helpers/admin-variants-seeder.js @@ -0,0 +1,192 @@ +const { + Region, + Product, + ProductVariant, + PriceList, + CustomerGroup, + Customer, + Image, + ShippingProfile, + ProductCollection, + ProductOption, +} = require("@medusajs/medusa") + +module.exports = async (connection, data = {}) => { + const manager = connection.manager + + const yesterday = ((today) => new Date(today.setDate(today.getDate() - 1)))( + new Date() + ) + + const tenDaysAgo = ((today) => new Date(today.setDate(today.getDate() - 10)))( + new Date() + ) + + const defaultProfile = await manager.findOne(ShippingProfile, { + type: "default", + }) + + const collection = manager.create(ProductCollection, { + id: "test-collection", + handle: "test-collection", + title: "Test collection", + }) + + await manager.save(collection) + + await manager.insert(Region, { + id: "reg-europe", + name: "Test Region Europe", + currency_code: "eur", + tax_rate: 0, + }) + + await manager.insert(Region, { + id: "reg-us", + name: "Test Region US", + currency_code: "usd", + tax_rate: 0, + }) + + const customer = await manager.create(Customer, { + id: "test-customer", + email: "john@doe.com", + first_name: "John", + last_name: "Doe", + password_hash: + "c2NyeXB0AAEAAAABAAAAAVMdaddoGjwU1TafDLLlBKnOTQga7P2dbrfgf3fB+rCD/cJOMuGzAvRdKutbYkVpuJWTU39P7OpuWNkUVoEETOVLMJafbI8qs8Qx/7jMQXkN", // password matching "test" + has_account: true, + }) + + const customerGroup = await manager.create(CustomerGroup, { + id: "test-group", + name: "test-group", + }) + + await manager.save(customerGroup) + customer.groups = [customerGroup] + await manager.save(customer) + + const priceListActive = await manager.create(PriceList, { + id: "pl", + name: "VIP sale", + description: "All year sale for VIP customers.", + type: "sale", + status: "active", + }) + + await manager.save(priceListActive) + + const priceListExpired = await manager.create(PriceList, { + id: "pl_expired", + name: "VIP summer sale", + description: "Summer sale for VIP customers.", + type: "sale", + status: "active", + starts_at: tenDaysAgo, + ends_at: yesterday, + }) + + await manager.save(priceListExpired) + + const priceListWithCustomers = await manager.create(PriceList, { + id: "pl_with_customers", + name: "VIP winter sale", + description: "Winter sale for VIP customers.", + type: "sale", + status: "active", + }) + + priceListWithCustomers.customer_groups = [customerGroup] + + await manager.save(priceListWithCustomers) + + const productMultiReg = manager.create(Product, { + id: "test-product", + handle: "test-product-reg", + title: "Multi Reg Test product", + profile_id: defaultProfile.id, + description: "test-product-description", + status: "published", + collection_id: "test-collection", + }) + + const image = manager.create(Image, { + id: "test-image", + url: "test-image.png", + }) + + productMultiReg.images = [image] + + await manager.save(productMultiReg) + + await manager.save(ProductOption, { + id: "test-option", + title: "test-option", + product_id: "test-product", + }) + + const variantMultiReg = await manager.create(ProductVariant, { + id: "test-variant", + inventory_quantity: 10, + title: "Test variant", + variant_rank: 0, + sku: "test-sku", + ean: "test-ean", + upc: "test-upc", + barcode: "test-barcode", + product_id: "test-product", + prices: [ + { + id: "test-price-multi-usd", + currency_code: "usd", + type: "default", + amount: 100, + }, + { + id: "test-price-discount-multi-usd", + currency_code: "usd", + amount: 80, + price_list_id: "pl", + }, + { + id: "test-price-discount-expired-multi-usd", + currency_code: "usd", + amount: 70, + price_list_id: "pl_expired", + }, + { + id: "test-price-multi-eur", + currency_code: "eur", + amount: 100, + }, + { + id: "test-price-discount-multi-eur", + currency_code: "eur", + amount: 80, + price_list_id: "pl", + }, + { + id: "test-price-discount-multi-eur-with-customer", + currency_code: "eur", + amount: 40, + price_list_id: "pl_with_customers", + }, + { + id: "test-price-discount-expired-multi-eur", + currency_code: "eur", + amount: 70, + price_list_id: "pl_expired", + }, + ], + options: [ + { + id: "test-variant-option-reg", + value: "Default variant", + option_id: "test-option", + }, + ], + }) + + await manager.save(variantMultiReg) +} diff --git a/integration-tests/api/helpers/store-product-seeder.js b/integration-tests/api/helpers/store-product-seeder.js index d653837f7fbd6..a675ec352eb22 100644 --- a/integration-tests/api/helpers/store-product-seeder.js +++ b/integration-tests/api/helpers/store-product-seeder.js @@ -371,125 +371,4 @@ module.exports = async (connection, data = {}) => { }) await manager.save(gift_card) - - await manager.insert(Region, { - id: "reg-europe", - name: "Test Region Europe", - currency_code: "eur", - tax_rate: 0, - }) - - const customer = await manager.create(Customer, { - id: "test-customer", - email: "john@doe.com", - first_name: "John", - last_name: "Doe", - password_hash: - "c2NyeXB0AAEAAAABAAAAAVMdaddoGjwU1TafDLLlBKnOTQga7P2dbrfgf3fB+rCD/cJOMuGzAvRdKutbYkVpuJWTU39P7OpuWNkUVoEETOVLMJafbI8qs8Qx/7jMQXkN", // password matching "test" - has_account: true, - }) - - const customerGroup = await manager.create(CustomerGroup, { - id: "test-group", - name: "test-group", - }) - - await manager.save(customerGroup) - customer.groups = [customerGroup] - await manager.save(customer) - - const priceListWithCustomers = await manager.create(PriceList, { - id: "pl_with_customers", - name: "VIP winter sale", - description: "Winter sale for VIP customers.", - type: "sale", - status: "active", - }) - - priceListWithCustomers.customer_groups = [customerGroup] - - await manager.save(priceListWithCustomers) - - const productMultiReg = manager.create(Product, { - id: "test-product-multi-reg", - handle: "test-product-reg", - title: "Multi Reg Test product", - profile_id: defaultProfile.id, - description: "test-product-description", - status: "published", - collection_id: "test-collection", - type: { id: "test-type", value: "test-type" }, - tags: [ - { id: "tag1", value: "123" }, - { tag: "tag2", value: "456" }, - ], - }) - - productMultiReg.images = [image] - - await manager.save(productMultiReg) - - const variantMultiReg = await manager.create(ProductVariant, { - id: "test-variant-multi-reg", - inventory_quantity: 10, - title: "Test variant", - variant_rank: 0, - sku: "test-sku-multi-reg", - ean: "test-ean-multi-reg", - upc: "test-upc-multi-reg", - barcode: "test-barcode_multi-reg", - product_id: "test-product-multi-reg", - prices: [ - { - id: "test-price-multi-usd", - currency_code: "usd", - type: "default", - amount: 100, - }, - { - id: "test-price-discount-multi-usd", - currency_code: "usd", - amount: 80, - price_list_id: "pl", - }, - { - id: "test-price-discount-expired-multi-usd", - currency_code: "usd", - amount: 70, - price_list_id: "pl_expired", - }, - { - id: "test-price-multi-eur", - currency_code: "eur", - amount: 100, - }, - { - id: "test-price-discount-multi-eur", - currency_code: "eur", - amount: 80, - price_list_id: "pl", - }, - { - id: "test-price-discount-multi-eur-with-customer", - currency_code: "eur", - amount: 40, - price_list_id: "pl_with_customers", - }, - { - id: "test-price-discount-expired-multi-eur", - currency_code: "eur", - amount: 70, - price_list_id: "pl_expired", - }, - ], - options: [ - { - id: "test-variant-option-reg", - value: "Default variant", - option_id: "test-option", - }, - ], - }) - - await manager.save(variantMultiReg) }