Feat: use price selection strategy (#1165)

* init

* add query params

* added buld id validation to repo

* admin done

* updated price reqs

* initial price selection strategy

* update customer seeder

* format models

* price selection strategy

* price selection testing

* update price selection tests

* update price selection strategy

* remove console.warn

* update price seleciton strategy

* use price selection strategy in "get-product"

* price selection strategy integration testing

* update integration tests

* update price selection strat

* remove console.log

* fix unit tests

* update product snapshot integration tests

* fix failing unit tests

* update variant test snapshots

* update variant test snapshots

* fix failing unit tests

* update product snapshot integration tests

* intial implementation of PriceList

* add price selection strategy test to list-products

* add price selection to list products

* add price selection strategy to getRegionPrice

* add price selection strategy to get variant

* update product snapshot test

* store testing of price selection

* variant services

* update cart service

* update product tests

* update test

* unit testing with price selection strategy

* integration tests for price lists

* update sort prices for consistent results

* update snapshot

* update product snapshot with product ids

* this time pipelines work

* swap tests

* redo ordering

* updated admin/product integration tests

* update updateVariantPrices method

* remove comment from error handler

* add integration test for batch deleting prices associated with price list

* named ids

* run with verbose

* add console.log

* sort products in integration test

* remove verbose flag

* make update to prices through variant service limited to default prices

* update store/products.js snapshot

* update comment

* add todo

* lift existing price calculations to variant level

* remove unused import

* add api unit tests and update product integration tests to validate that prices from Price List are ignored

* fix product test

* update integration tests

* pre merge commit

* requested changes

* cascade

* ensure delete variant cascades to MoneyAmount

* fetch variants correctly

* use find options

* add pricelist to relevant seeders

* update integration tests

* update price selection with "includeDiscountPrices"

* use transaction with price selection strategy

* add await to prevent store test errors

* remove verbose

* addresses PR feedback

* removed unused endpoint

* remove unused repository from constructor

* remove from constructor argument

* update mock

* update unit tests

* fix failing store integration tests

* remove medusajs ressource

* re add env.template

* price selection strategy methods

* fix integration tests

* update unit tests

* remove commented out code

* update jsdoc

* update price selection strategy parameter

* update snapshots

* integration tests cleanup

* pr feedback

* update integration tests

* pr feedback

* price selection unit tests

* add calculated price type to results

* cleanup include discount prices and price selection configs

* refactor price selection params to a separate file

* update tests and refactor price selection seeder

* remove prices from variant update

* rename result

* pr feedback

* remove unused import

* create getRegionPriceContext

* remove from params in search

* remove unused import

Co-authored-by: Kasper <kasper@medusajs.com>
Co-authored-by: Kasper Fabricius Kristensen <45367945+kasperkristensen@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2022-03-23 17:17:19 +01:00
1 parent 491b6eba2d
commit e2423020c0
36 files changed
+3794 -533

No files matched your search

@@ -21,7 +21,7 @@ Array [
"handle": "test-product", "handle": "test-product",
"height": null, "height": null,
"hs_code": null, "hs_code": null,
"id": StringMatching /\\^test-\\*/, "id": "test-product",
"images": Array [ "images": Array [
Object { Object {
"created_at": Any<String>, "created_at": Any<String>,
@@ -78,6 +78,7 @@ Array [
Object { Object {
"allow_backorder": false, "allow_backorder": false,
"barcode": "test-barcode", "barcode": "test-barcode",
"calculated_price": null,
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"ean": "test-ean", "ean": "test-ean",
@@ -103,15 +104,17 @@ Array [
}, },
], ],
"origin_country": null, "origin_country": null,
"original_price": null,
"prices": Array [ "prices": Array [
Object { Object {
"amount": 100, "amount": 100,
"created_at": Any<String>, "created_at": Any<String>,
"currency_code": "usd", "currency_code": "usd",
"deleted_at": null, "deleted_at": null,
"id": StringMatching /\\^test-price\\*/, "id": "test-price",
"max_quantity": null, "max_quantity": null,
"min_quantity": null, "min_quantity": null,
"price_list": null,
"price_list_id": null, "price_list_id": null,
"region_id": null, "region_id": null,
"updated_at": Any<String>, "updated_at": Any<String>,
@@ -129,6 +132,7 @@ Array [
Object { Object {
"allow_backorder": false, "allow_backorder": false,
"barcode": null, "barcode": null,
"calculated_price": null,
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"ean": "test-ean2", "ean": "test-ean2",
@@ -154,6 +158,7 @@ Array [
}, },
], ],
"origin_country": null, "origin_country": null,
"original_price": null,
"prices": Array [ "prices": Array [
Object { Object {
"amount": 100, "amount": 100,
@@ -163,10 +168,11 @@ Array [
"id": StringMatching /\\^test-price\\*/, "id": StringMatching /\\^test-price\\*/,
"max_quantity": null, "max_quantity": null,
"min_quantity": null, "min_quantity": null,
"price_list": null,
"price_list_id": null, "price_list_id": null,
"region_id": null, "region_id": null,
"updated_at": Any<String>, "updated_at": Any<String>,
"variant_id": StringMatching /\\^test-variant\\*/, "variant_id": "test-variant_2",
}, },
], ],
"product_id": StringMatching /\\^test-\\*/, "product_id": StringMatching /\\^test-\\*/,
@@ -180,6 +186,7 @@ Array [
Object { Object {
"allow_backorder": false, "allow_backorder": false,
"barcode": "test-barcode 1", "barcode": "test-barcode 1",
"calculated_price": null,
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"ean": "test-ean1", "ean": "test-ean1",
@@ -205,6 +212,7 @@ Array [
}, },
], ],
"origin_country": null, "origin_country": null,
"original_price": null,
"prices": Array [ "prices": Array [
Object { Object {
"amount": 100, "amount": 100,
@@ -214,6 +222,7 @@ Array [
"id": StringMatching /\\^test-price\\*/, "id": StringMatching /\\^test-price\\*/,
"max_quantity": null, "max_quantity": null,
"min_quantity": null, "min_quantity": null,
"price_list": null,
"price_list_id": null, "price_list_id": null,
"region_id": null, "region_id": null,
"updated_at": Any<String>, "updated_at": Any<String>,
@@ -228,6 +237,60 @@ Array [
"weight": null, "weight": null,
"width": null, "width": null,
}, },
Object {
"allow_backorder": false,
"barcode": "test-barcode-sale",
"calculated_price": null,
"created_at": Any<String>,
"deleted_at": null,
"ean": "test-ean-sale",
"height": null,
"hs_code": null,
"id": "test-variant-sale",
"inventory_quantity": 10,
"length": null,
"manage_inventory": true,
"material": null,
"metadata": null,
"mid_code": null,
"options": Array [
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": StringMatching /\\^test-variant-option\\*/,
"metadata": null,
"option_id": StringMatching /\\^test-opt\\*/,
"updated_at": Any<String>,
"value": "Default variant",
"variant_id": StringMatching /\\^test-variant\\*/,
},
],
"origin_country": null,
"original_price": null,
"prices": Array [
Object {
"amount": 1000,
"created_at": Any<String>,
"currency_code": "usd",
"deleted_at": null,
"id": "test-price-sale",
"max_quantity": null,
"min_quantity": null,
"price_list": null,
"price_list_id": null,
"region_id": null,
"updated_at": Any<String>,
"variant_id": StringMatching /\\^test-variant\\*/,
},
],
"product_id": StringMatching /\\^test-\\*/,
"sku": "test-sku-sale",
"title": "Test variant",
"upc": "test-upc-sale",
"updated_at": Any<String>,
"weight": null,
"width": null,
},
], ],
"weight": null, "weight": null,
"width": null, "width": null,
@@ -251,7 +314,7 @@ Array [
"handle": "test-product1", "handle": "test-product1",
"height": null, "height": null,
"hs_code": null, "hs_code": null,
"id": StringMatching /\\^test-\\*/, "id": "test-product1",
"images": Array [], "images": Array [],
"is_giftcard": false, "is_giftcard": false,
"length": null, "length": null,
@@ -289,6 +352,7 @@ Array [
Object { Object {
"allow_backorder": false, "allow_backorder": false,
"barcode": null, "barcode": null,
"calculated_price": null,
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"ean": "test-ean4", "ean": "test-ean4",
@@ -314,6 +378,7 @@ Array [
}, },
], ],
"origin_country": null, "origin_country": null,
"original_price": null,
"prices": Array [ "prices": Array [
Object { Object {
"amount": 100, "amount": 100,
@@ -323,6 +388,7 @@ Array [
"id": StringMatching /\\^test-price\\*/, "id": StringMatching /\\^test-price\\*/,
"max_quantity": null, "max_quantity": null,
"min_quantity": null, "min_quantity": null,
"price_list": null,
"price_list_id": null, "price_list_id": null,
"region_id": null, "region_id": null,
"updated_at": Any<String>, "updated_at": Any<String>,
@@ -340,6 +406,7 @@ Array [
Object { Object {
"allow_backorder": false, "allow_backorder": false,
"barcode": null, "barcode": null,
"calculated_price": null,
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"ean": "test-ean3", "ean": "test-ean3",
@@ -365,6 +432,7 @@ Array [
}, },
], ],
"origin_country": null, "origin_country": null,
"original_price": null,
"prices": Array [ "prices": Array [
Object { Object {
"amount": 100, "amount": 100,
@@ -374,6 +442,7 @@ Array [
"id": StringMatching /\\^test-price\\*/, "id": StringMatching /\\^test-price\\*/,
"max_quantity": null, "max_quantity": null,
"min_quantity": null, "min_quantity": null,
"price_list": null,
"price_list_id": null, "price_list_id": null,
"region_id": "test-region", "region_id": "test-region",
"updated_at": Any<String>, "updated_at": Any<String>,
@@ -392,39 +461,6 @@ Array [
"weight": null, "weight": null,
"width": null, "width": null,
}, },
Object {
"collection": Any<Object>,
"collection_id": "test-collection1",
"created_at": Any<String>,
"deleted_at": null,
"description": "test-product-description",
"discountable": true,
"external_id": null,
"handle": "test-product_filtering_3",
"height": null,
"hs_code": null,
"id": StringMatching /\\^test-\\*/,
"images": Array [],
"is_giftcard": false,
"length": null,
"material": null,
"metadata": null,
"mid_code": null,
"options": Any<Array>,
"origin_country": null,
"profile_id": StringMatching /\\^sp_\\*/,
"status": "draft",
"subtitle": null,
"tags": Any<Array>,
"thumbnail": null,
"title": "Test product filtering 3",
"type": Any<Object>,
"type_id": "test-type",
"updated_at": Any<String>,
"variants": Any<Array>,
"weight": null,
"width": null,
},
Object { Object {
"collection": Any<Object>, "collection": Any<Object>,
"collection_id": "test-collection1", "collection_id": "test-collection1",
@@ -436,7 +472,7 @@ Array [
"handle": "test-product_filtering_1", "handle": "test-product_filtering_1",
"height": null, "height": null,
"hs_code": null, "hs_code": null,
"id": StringMatching /\\^test-\\*/, "id": "test-product_filtering_1",
"images": Array [], "images": Array [],
"is_giftcard": false, "is_giftcard": false,
"length": null, "length": null,
@@ -469,7 +505,7 @@ Array [
"handle": "test-product_filtering_2", "handle": "test-product_filtering_2",
"height": null, "height": null,
"hs_code": null, "hs_code": null,
"id": StringMatching /\\^test-\\*/, "id": "test-product_filtering_2",
"images": Array [], "images": Array [],
"is_giftcard": false, "is_giftcard": false,
"length": null, "length": null,
@@ -491,6 +527,39 @@ Array [
"weight": null, "weight": null,
"width": null, "width": null,
}, },
Object {
"collection": Any<Object>,
"collection_id": "test-collection1",
"created_at": Any<String>,
"deleted_at": null,
"description": "test-product-description",
"discountable": true,
"external_id": null,
"handle": "test-product_filtering_3",
"height": null,
"hs_code": null,
"id": "test-product_filtering_3",
"images": Array [],
"is_giftcard": false,
"length": null,
"material": null,
"metadata": null,
"mid_code": null,
"options": Any<Array>,
"origin_country": null,
"profile_id": StringMatching /\\^sp_\\*/,
"status": "draft",
"subtitle": null,
"tags": Any<Array>,
"thumbnail": null,
"title": "Test product filtering 3",
"type": Any<Object>,
"type_id": "test-type",
"updated_at": Any<String>,
"variants": Any<Array>,
"weight": null,
"width": null,
},
] ]
`; `;
@@ -539,6 +608,7 @@ Array [
Object { Object {
"allow_backorder": false, "allow_backorder": false,
"barcode": null, "barcode": null,
"calculated_price": null,
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"ean": null, "ean": null,
@@ -564,6 +634,7 @@ Array [
}, },
], ],
"origin_country": null, "origin_country": null,
"original_price": null,
"prices": Array [ "prices": Array [
Object { Object {
"amount": 100, "amount": 100,
@@ -573,6 +644,7 @@ Array [
"id": Any<String>, "id": Any<String>,
"max_quantity": null, "max_quantity": null,
"min_quantity": null, "min_quantity": null,
"price_list": null,
"price_list_id": null, "price_list_id": null,
"region_id": null, "region_id": null,
"updated_at": Any<String>, "updated_at": Any<String>,
@@ -696,6 +768,7 @@ Object {
Object { Object {
"allow_backorder": false, "allow_backorder": false,
"barcode": null, "barcode": null,
"calculated_price": null,
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"ean": null, "ean": null,
@@ -731,6 +804,7 @@ Object {
}, },
], ],
"origin_country": null, "origin_country": null,
"original_price": null,
"prices": Array [ "prices": Array [
Object { Object {
"amount": 100, "amount": 100,
@@ -740,6 +814,7 @@ Object {
"id": StringMatching /\\^ma_\\*/, "id": StringMatching /\\^ma_\\*/,
"max_quantity": null, "max_quantity": null,
"min_quantity": null, "min_quantity": null,
"price_list": null,
"price_list_id": null, "price_list_id": null,
"region_id": null, "region_id": null,
"updated_at": Any<String>, "updated_at": Any<String>,
@@ -753,6 +828,7 @@ Object {
"id": StringMatching /\\^ma_\\*/, "id": StringMatching /\\^ma_\\*/,
"max_quantity": null, "max_quantity": null,
"min_quantity": null, "min_quantity": null,
"price_list": null,
"price_list_id": null, "price_list_id": null,
"region_id": null, "region_id": null,
"updated_at": Any<String>, "updated_at": Any<String>,
@@ -766,6 +842,7 @@ Object {
"id": StringMatching /\\^ma_\\*/, "id": StringMatching /\\^ma_\\*/,
"max_quantity": null, "max_quantity": null,
"min_quantity": null, "min_quantity": null,
"price_list": null,
"price_list_id": null, "price_list_id": null,
"region_id": null, "region_id": null,
"updated_at": Any<String>, "updated_at": Any<String>,
@@ -855,6 +932,7 @@ Object {
Object { Object {
"allow_backorder": false, "allow_backorder": false,
"barcode": "test-barcode", "barcode": "test-barcode",
"calculated_price": null,
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"ean": "test-ean", "ean": "test-ean",
@@ -880,6 +958,7 @@ Object {
}, },
], ],
"origin_country": null, "origin_country": null,
"original_price": null,
"prices": Array [ "prices": Array [
Object { Object {
"amount": 75, "amount": 75,
@@ -889,6 +968,7 @@ Object {
"id": "test-price", "id": "test-price",
"max_quantity": null, "max_quantity": null,
"min_quantity": null, "min_quantity": null,
"price_list": null,
"price_list_id": null, "price_list_id": null,
"region_id": null, "region_id": null,
"updated_at": Any<String>, "updated_at": Any<String>,
+66 -152
View File
@@ -522,10 +522,13 @@ describe("/admin/products", () => {
console.log(err) console.log(err)
}) })
response.data.products.sort((a, b) =>
a.created_at > b.created_at ? 1 : -1
)
expect(response.data.products).toMatchSnapshot([ expect(response.data.products).toMatchSnapshot([
{ {
id: expect.stringMatching(/^test-*/), id: "test-product",
created_at: expect.any(String),
options: [ options: [
{ {
id: expect.stringMatching(/^test-*/), id: expect.stringMatching(/^test-*/),
@@ -543,7 +546,53 @@ describe("/admin/products", () => {
], ],
variants: [ variants: [
{ {
id: "test-variant", // expect.stringMatching(/^test-variant*/), id: "test-variant",
created_at: expect.any(String),
updated_at: expect.any(String),
product_id: expect.stringMatching(/^test-*/),
prices: [
{
id: "test-price",
variant_id: expect.stringMatching(/^test-variant*/),
created_at: expect.any(String),
updated_at: expect.any(String),
},
],
options: [
{
id: expect.stringMatching(/^test-variant-option*/),
variant_id: expect.stringMatching(/^test-variant*/),
option_id: expect.stringMatching(/^test-opt*/),
created_at: expect.any(String),
updated_at: expect.any(String),
},
],
},
{
id: "test-variant_2",
created_at: expect.any(String),
updated_at: expect.any(String),
product_id: expect.stringMatching(/^test-*/),
prices: [
{
id: expect.stringMatching(/^test-price*/),
variant_id: "test-variant_2",
created_at: expect.any(String),
updated_at: expect.any(String),
},
],
options: [
{
id: expect.stringMatching(/^test-variant-option*/),
variant_id: expect.stringMatching(/^test-variant*/),
option_id: expect.stringMatching(/^test-opt*/),
created_at: expect.any(String),
updated_at: expect.any(String),
},
],
},
{
id: "test-variant_1",
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
product_id: expect.stringMatching(/^test-*/), product_id: expect.stringMatching(/^test-*/),
@@ -566,36 +615,13 @@ describe("/admin/products", () => {
], ],
}, },
{ {
id: "test-variant_2", // expect.stringMatching(/^test-variant*/), id: "test-variant-sale",
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
product_id: expect.stringMatching(/^test-*/), product_id: expect.stringMatching(/^test-*/),
prices: [ prices: [
{ {
id: expect.stringMatching(/^test-price*/), id: "test-price-sale",
variant_id: expect.stringMatching(/^test-variant*/),
created_at: expect.any(String),
updated_at: expect.any(String),
},
],
options: [
{
id: expect.stringMatching(/^test-variant-option*/),
variant_id: expect.stringMatching(/^test-variant*/),
option_id: expect.stringMatching(/^test-opt*/),
created_at: expect.any(String),
updated_at: expect.any(String),
},
],
},
{
id: "test-variant_1", // expect.stringMatching(/^test-variant*/),
created_at: expect.any(String),
updated_at: expect.any(String),
product_id: expect.stringMatching(/^test-*/),
prices: [
{
id: expect.stringMatching(/^test-price*/),
variant_id: expect.stringMatching(/^test-variant*/), variant_id: expect.stringMatching(/^test-variant*/),
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
@@ -634,12 +660,12 @@ describe("/admin/products", () => {
updated_at: expect.any(String), updated_at: expect.any(String),
}, },
{ {
id: expect.stringMatching(/^test-*/), id: "test-product1",
created_at: expect.any(String), created_at: expect.any(String),
options: [], options: [],
variants: [ variants: [
{ {
id: "test-variant_4", // expect.stringMatching(/^test-variant*/), id: "test-variant_4",
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
product_id: expect.stringMatching(/^test-*/), product_id: expect.stringMatching(/^test-*/),
@@ -662,7 +688,7 @@ describe("/admin/products", () => {
], ],
}, },
{ {
id: "test-variant_3", // expect.stringMatching(/^test-variant*/), id: "test-variant_3",
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
product_id: expect.stringMatching(/^test-*/), product_id: expect.stringMatching(/^test-*/),
@@ -703,11 +729,10 @@ describe("/admin/products", () => {
updated_at: expect.any(String), updated_at: expect.any(String),
}, },
profile_id: expect.stringMatching(/^sp_*/), profile_id: expect.stringMatching(/^sp_*/),
created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
}, },
{ {
id: expect.stringMatching(/^test-*/), id: "test-product_filtering_1",
profile_id: expect.stringMatching(/^sp_*/), profile_id: expect.stringMatching(/^sp_*/),
created_at: expect.any(String), created_at: expect.any(String),
type: expect.any(Object), type: expect.any(Object),
@@ -718,7 +743,7 @@ describe("/admin/products", () => {
updated_at: expect.any(String), updated_at: expect.any(String),
}, },
{ {
id: expect.stringMatching(/^test-*/), id: "test-product_filtering_2",
profile_id: expect.stringMatching(/^sp_*/), profile_id: expect.stringMatching(/^sp_*/),
created_at: expect.any(String), created_at: expect.any(String),
type: expect.any(Object), type: expect.any(Object),
@@ -729,7 +754,7 @@ describe("/admin/products", () => {
updated_at: expect.any(String), updated_at: expect.any(String),
}, },
{ {
id: expect.stringMatching(/^test-*/), id: "test-product_filtering_3",
profile_id: expect.stringMatching(/^sp_*/), profile_id: expect.stringMatching(/^sp_*/),
created_at: expect.any(String), created_at: expect.any(String),
type: expect.any(Object), type: expect.any(Object),
@@ -1357,7 +1382,6 @@ describe("/admin/products", () => {
}) })
expect(response.status).toEqual(200) expect(response.status).toEqual(200)
expect(response.data).toEqual({ expect(response.data).toEqual({
product: expect.objectContaining({ product: expect.objectContaining({
id: "test-product", id: "test-product",
@@ -1369,33 +1393,6 @@ describe("/admin/products", () => {
amount: 1500, amount: 1500,
currency_code: "usd", currency_code: "usd",
}), }),
expect.objectContaining({
id: "ma_test_1",
amount: 100,
currency_code: "usd",
min_quantity: 1,
max_quantity: 100,
variant_id: "test-variant",
price_list_id: "pl_no_customer_groups",
}),
expect.objectContaining({
id: "ma_test_2",
amount: 80,
currency_code: "usd",
min_quantity: 101,
max_quantity: 500,
variant_id: "test-variant",
price_list_id: "pl_no_customer_groups",
}),
expect.objectContaining({
id: "ma_test_3",
amount: 50,
currency_code: "usd",
min_quantity: 501,
max_quantity: 1000,
variant_id: "test-variant",
price_list_id: "pl_no_customer_groups",
}),
]), ]),
}), }),
]), ]),
@@ -1451,8 +1448,9 @@ describe("/admin/products", () => {
prices: [ prices: [
// usd price coming from the product seeder // usd price coming from the product seeder
{ {
currency_code: "usd", id: "test-price",
amount: 100, amount: 100,
currency_code: "usd",
}, },
{ {
currency_code: "eur", currency_code: "eur",
@@ -1489,33 +1487,6 @@ describe("/admin/products", () => {
amount: 4500, amount: 4500,
currency_code: "eur", currency_code: "eur",
}), }),
expect.objectContaining({
id: "ma_test_1",
amount: 100,
currency_code: "usd",
min_quantity: 1,
max_quantity: 100,
variant_id: "test-variant",
price_list_id: "pl_no_customer_groups",
}),
expect.objectContaining({
id: "ma_test_2",
amount: 80,
currency_code: "usd",
min_quantity: 101,
max_quantity: 500,
variant_id: "test-variant",
price_list_id: "pl_no_customer_groups",
}),
expect.objectContaining({
id: "ma_test_3",
amount: 50,
currency_code: "usd",
min_quantity: 501,
max_quantity: 1000,
variant_id: "test-variant",
price_list_id: "pl_no_customer_groups",
}),
]), ]),
}), }),
]), ]),
@@ -1547,42 +1518,13 @@ describe("/admin/products", () => {
expect(response.status).toEqual(200) expect(response.status).toEqual(200)
expect(response.data.product.variants[0].prices.length).toEqual( expect(response.data.product.variants[0].prices.length).toEqual(1)
4 // 3 prices from Price List + 1 default price
)
expect(response.data.product.variants[0].prices).toEqual( expect(response.data.product.variants[0].prices).toEqual(
expect.arrayContaining([ expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
amount: 4500, amount: 4500,
currency_code: "usd", currency_code: "usd",
}), }),
expect.objectContaining({
id: "ma_test_1",
amount: 100,
currency_code: "usd",
min_quantity: 1,
max_quantity: 100,
variant_id: "test-variant",
price_list_id: "pl_no_customer_groups",
}),
expect.objectContaining({
id: "ma_test_2",
amount: 80,
currency_code: "usd",
min_quantity: 101,
max_quantity: 500,
variant_id: "test-variant",
price_list_id: "pl_no_customer_groups",
}),
expect.objectContaining({
id: "ma_test_3",
amount: 50,
currency_code: "usd",
min_quantity: 501,
max_quantity: 1000,
variant_id: "test-variant",
price_list_id: "pl_no_customer_groups",
}),
]) ])
) )
}) })
@@ -1614,9 +1556,8 @@ describe("/admin/products", () => {
expect(response.status).toEqual(200) expect(response.status).toEqual(200)
expect(response.data.product.variants[0].prices.length).toEqual( expect(response.data.product.variants[0].prices.length).toEqual(2)
5 // 2 default prices + 3 prices from Price List
)
expect(response.data.product.variants[0].prices).toEqual( expect(response.data.product.variants[0].prices).toEqual(
expect.arrayContaining([ expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
@@ -1627,33 +1568,6 @@ describe("/admin/products", () => {
amount: 900, amount: 900,
currency_code: "eur", currency_code: "eur",
}), }),
expect.objectContaining({
id: "ma_test_1",
amount: 100,
currency_code: "usd",
min_quantity: 1,
max_quantity: 100,
variant_id: "test-variant",
price_list_id: "pl_no_customer_groups",
}),
expect.objectContaining({
id: "ma_test_2",
amount: 80,
currency_code: "usd",
min_quantity: 101,
max_quantity: 500,
variant_id: "test-variant",
price_list_id: "pl_no_customer_groups",
}),
expect.objectContaining({
id: "ma_test_3",
amount: 50,
currency_code: "usd",
min_quantity: 501,
max_quantity: 1000,
variant_id: "test-variant",
price_list_id: "pl_no_customer_groups",
}),
]) ])
) )
}) })
@@ -1951,7 +1865,7 @@ describe("/admin/products", () => {
it("successfully creates soft-deleted product variant", async () => { it("successfully creates soft-deleted product variant", async () => {
const api = useApi() const api = useApi()
const product = await api await api
.get("/admin/products/test-product", { .get("/admin/products/test-product", {
headers: { headers: {
Authorization: "bearer test_token", Authorization: "bearer test_token",
@@ -0,0 +1,659 @@
const path = require("path")
const setupServer = require("../../../helpers/setup-server")
const { useApi } = require("../../../helpers/use-api")
const { useDb, initDb } = require("../../../helpers/use-db")
const adminSeeder = require("../../helpers/admin-seeder")
const promotionsSeeder = require("../../helpers/price-selection-seeder")
jest.setTimeout(30000)
describe("Promotions", () => {
let medusaProcess
let dbConnection
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."))
dbConnection = await initDb({ cwd })
medusaProcess = await setupServer({ cwd })
})
afterAll(async () => {
const db = useDb()
await db.shutdown()
medusaProcess.kill()
})
describe("Money amount", () => {
beforeEach(async () => {
try {
await adminSeeder(dbConnection)
await promotionsSeeder(dbConnection)
} catch (err) {
console.log(err)
throw err
}
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("calculated_price contains lowest price", async () => {
const api = useApi()
const res = await api
.get("/store/products/test-product?region_id=test-region")
.catch((error) => console.log(error))
const variant = res.data.product.variants[0]
const lowestPrice = variant.prices.reduce(
(prev, curr) => (curr.amount < prev ? curr.amount : prev),
Infinity
)
expect(variant.calculated_price).toEqual(lowestPrice)
expect(variant).toEqual(
expect.objectContaining({ original_price: 120, calculated_price: 110 })
)
})
it("returns no money amounts belonging to customer groups without login", async () => {
const api = useApi()
const res = await api
.get("/store/products/test-product?cart_id=test-cart")
.catch((error) => console.log(error))
const variant = res.data.product.variants[0]
expect(variant.prices.length).toEqual(2)
variant.prices.forEach((price) => {
if (price.price_list) {
expect(price.price_list.customer_groups).toEqual(undefined)
} else {
expect(price.price_list).toEqual(null)
}
})
expect(variant.prices).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: "test-price1",
region_id: "test-region",
currency_code: "usd",
amount: 120,
}),
expect.objectContaining({
id: "test-price3",
region_id: "test-region",
currency_code: "usd",
price_list_id: "pl",
amount: 110,
}),
])
)
})
it("sets default price as original price", async () => {
const api = useApi()
const res = await api
.get("/store/products/test-product?cart_id=test-cart")
.catch((error) => console.log(error))
const variant = res.data.product.variants[0]
expect(variant.original_price).toEqual(
variant.prices.find((p) => p.price_list_id === null).amount
)
})
it("gets prices for currency if no region prices exist", async () => {
const api = useApi()
const res = await api
.get("/store/products/test-product?cart_id=test-cart-2")
.catch((error) => console.log(error))
const variant = res.data.product.variants[0]
expect(variant.original_price).toEqual(
variant.prices.find((p) => p.price_list_id === null).amount
)
expect(variant.prices.length).toEqual(2)
variant.prices.forEach((price) => {
if (price.price_list) {
expect(price.price_list.customer_groups).toEqual(undefined)
} else {
expect(price.price_list).toEqual(null)
}
})
variant.prices.forEach((price) => {
expect(price.region_id).toEqual("test-region")
expect(price.currency_code).toEqual("usd")
})
expect(variant.prices).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: "test-price1",
region_id: "test-region",
currency_code: "usd",
amount: 120,
}),
expect.objectContaining({
id: "test-price3",
region_id: "test-region",
currency_code: "usd",
price_list_id: "pl",
amount: 110,
}),
])
)
})
it("gets prices for cart region for multi region product", async () => {
const api = useApi()
const res = await api
.get("/store/products/test-product-multi-region?cart_id=test-cart-1")
.catch((error) => console.log(error))
const variant = res.data.product.variants[0]
expect(variant).toEqual(
expect.objectContaining({ original_price: 130, calculated_price: 110 })
)
expect(variant.original_price).toEqual(
variant.prices.find((p) => p.price_list_id === null).amount
)
expect(variant.prices.length).toEqual(2)
expect(variant.prices).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: "test-price1-region-2",
region_id: "test-region-2",
currency_code: "dkk",
amount: 130,
}),
expect.objectContaining({
id: "test-price3-region-2",
region_id: "test-region-2",
currency_code: "dkk",
price_list_id: "pl",
amount: 110,
}),
])
)
})
it("gets prices for multi region product", async () => {
const api = useApi()
const res = await api
.get(
"/store/products/test-product-multi-region?region_id=test-region-2"
)
.catch((error) => console.log(error))
const variant = res.data.product.variants[0]
expect(variant).toEqual(
expect.objectContaining({ original_price: 130, calculated_price: 110 })
)
expect(variant.original_price).toEqual(
variant.prices.find((p) => p.price_list_id === null).amount
)
expect(variant.prices.length).toEqual(2)
expect(variant.prices).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: "test-price1-region-2",
region_id: "test-region-2",
currency_code: "dkk",
amount: 130,
}),
expect.objectContaining({
id: "test-price3-region-2",
region_id: "test-region-2",
currency_code: "dkk",
price_list_id: "pl",
amount: 110,
}),
])
)
})
it("gets prices for multi currency product", async () => {
const api = useApi()
const res = await api
.get("/store/products/test-product-multi-region?currency_code=dkk")
.catch((error) => console.log(error))
const variant = res.data.product.variants[0]
expect(variant).toEqual(
expect.objectContaining({ original_price: 130, calculated_price: 110 })
)
expect(variant.original_price).toEqual(
variant.prices.find((p) => p.price_list_id === null).amount
)
expect(variant.prices.length).toEqual(2)
expect(variant.prices).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: "test-price1-region-2",
region_id: "test-region-2",
currency_code: "dkk",
amount: 130,
}),
expect.objectContaining({
id: "test-price3-region-2",
region_id: "test-region-2",
currency_code: "dkk",
price_list_id: "pl",
amount: 110,
}),
])
)
})
it("gets moneyamounts only with valid date interval", async () => {
const api = useApi()
const res = await api
.get("/store/products/test-product-sale?cart_id=test-cart")
.catch((error) => console.log(error))
const variant = res.data.product.variants[0]
const date = new Date()
expect(variant.prices.length).toEqual(2)
variant.prices.forEach((price) => {
if (price.starts_at) {
expect(new Date(price.starts_at).getTime()).toBeLessThan(
date.getTime()
)
}
if (price.ends_at) {
expect(new Date(price.ends_at).getTime()).toBeGreaterThan(
date.getTime()
)
}
})
})
it("gets moneyamounts with valid date intervals and finds lowest price with overlapping intervals", async () => {
const api = useApi()
const res = await api
.get("/store/products/test-product-sale-overlap?cart_id=test-cart")
.catch((error) => console.log(error))
const variant = res.data.product.variants[0]
const date = new Date()
expect(variant).toEqual(
expect.objectContaining({
original_price: 150,
calculated_price: 120,
})
)
expect(variant.prices.length).toEqual(3)
variant.prices.forEach((price) => {
if (price.starts_at) {
expect(new Date(price.starts_at).getTime()).toBeLessThan(
date.getTime()
)
}
if (price.ends_at) {
expect(new Date(price.ends_at).getTime()).toBeGreaterThan(
date.getTime()
)
}
})
expect(variant.prices).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: "test-price-sale-overlap-1",
region_id: "test-region",
currency_code: "usd",
amount: 140,
price_list_id: "pl_current_1",
}),
expect.objectContaining({
id: "test-price1-sale-overlap",
region_id: "test-region",
currency_code: "usd",
amount: 120,
price_list_id: "pl_current",
}),
expect.objectContaining({
id: "test-price2-sale-overlap-default",
region_id: "test-region",
currency_code: "usd",
amount: 150,
}),
])
)
})
it("gets all prices with varying quantity limits with no quantity", async () => {
const api = useApi()
const res = await api
.get("/store/products/test-product-quantity?cart_id=test-cart")
.catch((error) => console.log(error))
const variant = res.data.product.variants[0]
expect(variant.prices.length).toEqual(5)
expect(variant.prices).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: "test-price-quantity",
region_id: "test-region",
currency_code: "usd",
amount: 100,
price_list_id: "pl",
min_quantity: 10,
max_quantity: 100,
}),
expect.objectContaining({
id: "test-price1-quantity",
region_id: "test-region",
currency_code: "usd",
amount: 120,
price_list_id: "pl",
min_quantity: 101,
max_quantity: 1000,
}),
expect.objectContaining({
id: "test-price2-quantity",
region_id: "test-region",
currency_code: "usd",
amount: 130,
price_list_id: "pl",
max_quantity: 9,
}),
expect.objectContaining({
id: "test-price3-quantity-now",
region_id: "test-region",
currency_code: "usd",
amount: 140,
price_list_id: "pl_current",
min_quantity: 101,
max_quantity: 1000,
}),
expect.objectContaining({
id: "test-price3-quantity-default",
region_id: "test-region",
currency_code: "usd",
amount: 150,
}),
])
)
expect(variant.calculated_price).toEqual(130)
expect(variant.original_price).toEqual(150)
expect(variant.original_price).toEqual(
variant.prices.find((p) => p.price_list_id === null).amount
)
})
it("fetches product with groups in money amounts with login", async () => {
const api = useApi()
// customer with customer-group 5
const authResponse = await api.post("/store/auth", {
email: "test5@email.com",
password: "test",
})
const [authCookie] = authResponse.headers["set-cookie"][0].split(";")
const res = await api
.get("/store/products/test-product?cart_id=test-cart", {
headers: {
Cookie: authCookie,
},
})
.catch((error) => console.log(error))
const variant = res.data.product.variants[0]
expect(variant.prices.length).toEqual(3)
expect(variant.prices).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: "test-price1",
region_id: "test-region",
currency_code: "usd",
amount: 120,
}),
expect.objectContaining({
id: "test-price3",
region_id: "test-region",
currency_code: "usd",
price_list_id: "pl",
amount: 110,
}),
expect.objectContaining({
id: "test-price",
region_id: "test-region",
currency_code: "usd",
amount: 100,
price_list: expect.objectContaining({}),
}),
])
)
})
it("fetches product with groups and quantities in money amounts with login", async () => {
const api = useApi()
// customer with customer-group 5
const authResponse = await api.post("/store/auth", {
email: "test5@email.com",
password: "test",
})
const [authCookie] = authResponse.headers["set-cookie"][0].split(";")
const res = await api
.get(
"/store/products/test-product-quantity-customer?cart_id=test-cart",
{
headers: {
Cookie: authCookie,
},
}
)
.catch((error) => console.log(error))
const variant = res.data.product.variants[0]
expect(variant.prices.length).toEqual(6)
expect(variant.prices).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: "test-price-quantity-customer",
region_id: "test-region",
currency_code: "usd",
amount: 100,
min_quantity: 10,
max_quantity: 100,
}),
expect.objectContaining({
id: "test-price1-quantity-customer",
region_id: "test-region",
currency_code: "usd",
amount: 120,
min_quantity: 101,
max_quantity: 1000,
}),
expect.objectContaining({
id: "test-price2-quantity-customer",
region_id: "test-region",
currency_code: "usd",
amount: 130,
max_quantity: 9,
}),
expect.objectContaining({
id: "test-price2-quantity-customer-group",
region_id: "test-region",
currency_code: "usd",
amount: 100,
max_quantity: 9,
price_list: expect.objectContaining({}),
}),
expect.objectContaining({
id: "test-price3-quantity-customer-now",
region_id: "test-region",
currency_code: "usd",
amount: 140,
min_quantity: 101,
max_quantity: 1000,
}),
expect.objectContaining({
id: "test-price3-quantity-customer-default",
region_id: "test-region",
currency_code: "usd",
amount: 150,
price_list_id: null,
}),
])
)
expect(variant.calculated_price).toEqual(100)
expect(variant.original_price).toEqual(150)
expect(variant.original_price).toEqual(
variant.prices.find((p) => p.price_list_id === null).amount
)
})
it("gets moneyamounts only with valid date interval for customer", async () => {
const api = useApi()
// customer with customer-group 5
const authResponse = await api.post("/store/auth", {
email: "test5@email.com",
password: "test",
})
const [authCookie] = authResponse.headers["set-cookie"][0].split(";")
const res = await api
.get("/store/products/test-product-sale-customer?cart_id=test-cart", {
headers: {
Cookie: authCookie,
},
})
.catch((error) => console.log(error))
const variant = res.data.product.variants[0]
const date = new Date()
expect(variant).toEqual(
expect.objectContaining({
original_price: 150,
calculated_price: 100,
})
)
expect(variant.prices.length).toEqual(2)
variant.prices.forEach((price) => {
if (price.starts_at) {
expect(new Date(price.starts_at).getTime()).toBeLessThan(
date.getTime()
)
}
if (price.ends_at) {
expect(new Date(price.ends_at).getTime()).toBeGreaterThan(
date.getTime()
)
}
})
})
it("gets moneyamounts only with valid date interval for customer regardless of quantity limits", async () => {
const api = useApi()
// customer with customer-group 5
const authResponse = await api.post("/store/auth", {
email: "test5@email.com",
password: "test",
})
const [authCookie] = authResponse.headers["set-cookie"][0].split(";")
const res = await api
.get(
"/store/products/test-product-sale-customer-quantity?cart_id=test-cart",
{
headers: {
Cookie: authCookie,
},
}
)
.catch((error) => console.log(error))
const variant = res.data.product.variants[0]
const date = new Date()
expect(variant).toEqual(
expect.objectContaining({
original_price: 150,
calculated_price: 100,
})
)
expect(variant.prices.length).toEqual(3)
variant.prices.forEach((price) => {
if (price.starts_at) {
expect(new Date(price.starts_at).getTime()).toBeLessThan(
date.getTime()
)
}
if (price.ends_at) {
expect(new Date(price.ends_at).getTime()).toBeGreaterThan(
date.getTime()
)
}
})
expect(variant.prices).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: "test-price1-sale-customer-quantity-groups",
region_id: "test-region",
currency_code: "usd",
amount: 100,
max_quantity: 99,
price_list: expect.objectContaining({}),
}),
expect.objectContaining({
id: "test-price2-sale-customer-quantity-default",
region_id: "test-region",
currency_code: "usd",
amount: 150,
}),
expect.objectContaining({
id: "test-price1-sale-customer-quantity",
region_id: "test-region",
currency_code: "usd",
amount: 110,
max_quantity: 99,
}),
])
)
})
})
})
@@ -1,10 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`/store/variants /test-variant 1`] = ` exports[`/store/variants GET /store/variants /test-variant 1`] = `
Object { Object {
"variant": Object { "variant": Object {
"allow_backorder": false, "allow_backorder": false,
"barcode": "test-barcode", "barcode": "test-barcode",
"calculated_price": null,
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"ean": "test-ean", "ean": "test-ean",
@@ -30,6 +31,7 @@ Object {
}, },
], ],
"origin_country": null, "origin_country": null,
"original_price": null,
"prices": Array [ "prices": Array [
Object { Object {
"amount": 100, "amount": 100,
@@ -39,6 +41,7 @@ Object {
"id": "test-price", "id": "test-price",
"max_quantity": null, "max_quantity": null,
"min_quantity": null, "min_quantity": null,
"price_list": null,
"price_list_id": null, "price_list_id": null,
"region_id": null, "region_id": null,
"updated_at": Any<String>, "updated_at": Any<String>,
@@ -57,71 +60,99 @@ Object {
} }
`; `;
exports[`/store/variants includes default relations 1`] = ` exports[`/store/variants GET /store/variants advanced pricing /test-variant with currency code 1`] = `
Object { Object {
"variants": Array [ "variant": Object {
Object { "allow_backorder": false,
"allow_backorder": false, "barcode": "test-barcode",
"barcode": "test-barcode", "calculated_price": 80,
"created_at": Any<String>, "calculated_price_type": "sale",
"deleted_at": null, "created_at": Any<String>,
"ean": "test-ean", "deleted_at": null,
"height": null, "ean": "test-ean",
"hs_code": null, "height": null,
"id": "test-variant", "hs_code": null,
"inventory_quantity": 10, "id": "test-variant",
"length": null, "inventory_quantity": 10,
"manage_inventory": true, "length": null,
"material": null, "manage_inventory": true,
"metadata": null, "material": null,
"mid_code": null, "metadata": null,
"options": Array [ "mid_code": null,
Object { "options": Array [
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": "test-variant-option",
"metadata": null,
"option_id": "test-option",
"updated_at": Any<String>,
"value": "Default variant",
"variant_id": "test-variant",
},
],
"origin_country": null,
"original_price": 100,
"prices": Array [
Object {
"amount": 100,
"created_at": Any<String>,
"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<String>,
"variant_id": "test-variant",
},
Object {
"amount": 80,
"created_at": Any<String>,
"currency_code": "usd",
"deleted_at": null,
"id": "test-price-discount",
"max_quantity": null,
"min_quantity": null,
"price_list": Object {
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"id": "test-variant-option", "description": "Winter sale for VIP customers.",
"metadata": null, "ends_at": null,
"option_id": "test-option", "id": "pl",
"name": "VIP winter sale",
"starts_at": null,
"status": "active",
"type": "sale",
"updated_at": Any<String>, "updated_at": Any<String>,
"value": "Default variant",
"variant_id": "test-variant",
}, },
], "price_list_id": "pl",
"origin_country": null, "region_id": null,
"prices": Array [ "updated_at": Any<String>,
Object { "variant_id": "test-variant",
"amount": 100, },
"created_at": Any<String>, ],
"currency_code": "usd", "product": Any<Object>,
"deleted_at": null, "product_id": "test-product",
"id": "test-price", "sku": "test-sku",
"max_quantity": null, "title": "Test variant",
"min_quantity": null, "upc": "test-upc",
"price_list_id": null, "updated_at": Any<String>,
"region_id": null, "weight": null,
"updated_at": Any<String>, "width": null,
"variant_id": "test-variant", },
},
],
"product": Any<Object>,
"product_id": "test-product",
"sku": "test-sku",
"title": "Test variant",
"upc": "test-upc",
"updated_at": Any<String>,
"weight": null,
"width": null,
},
],
} }
`; `;
exports[`/store/variants lists by title 1`] = ` exports[`/store/variants GET /store/variants advanced pricing lists by title 1`] = `
Object { Object {
"variants": Array [ "variants": Array [
Object { Object {
"allow_backorder": false, "allow_backorder": false,
"barcode": null, "barcode": null,
"calculated_price": null,
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"ean": null, "ean": null,
@@ -147,6 +178,7 @@ Object {
}, },
], ],
"origin_country": null, "origin_country": null,
"original_price": null,
"prices": Array [ "prices": Array [
Object { Object {
"amount": 100, "amount": 100,
@@ -156,6 +188,7 @@ Object {
"id": Any<String>, "id": Any<String>,
"max_quantity": null, "max_quantity": null,
"min_quantity": null, "min_quantity": null,
"price_list": null,
"price_list_id": null, "price_list_id": null,
"region_id": null, "region_id": null,
"updated_at": Any<String>, "updated_at": Any<String>,
@@ -174,3 +207,151 @@ Object {
], ],
} }
`; `;
exports[`/store/variants GET /store/variants advanced pricing sets additional prices correctly when querying a variant 1`] = `
Object {
"variant": Object {
"allow_backorder": false,
"barcode": "test-barcode",
"calculated_price": 80,
"calculated_price_type": "sale",
"created_at": 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,
"options": Array [
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": "test-variant-option",
"metadata": null,
"option_id": "test-option",
"updated_at": Any<String>,
"value": "Default variant",
"variant_id": "test-variant",
},
],
"origin_country": null,
"original_price": 100,
"prices": Array [
Object {
"amount": 100,
"created_at": Any<String>,
"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<String>,
"variant_id": "test-variant",
},
Object {
"amount": 80,
"created_at": Any<String>,
"currency_code": "usd",
"deleted_at": null,
"id": "test-price-discount",
"max_quantity": null,
"min_quantity": null,
"price_list": Object {
"created_at": Any<String>,
"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<String>,
},
"price_list_id": "pl",
"region_id": null,
"updated_at": Any<String>,
"variant_id": "test-variant",
},
],
"product": Any<Object>,
"product_id": "test-product",
"sku": "test-sku",
"title": "Test variant",
"upc": "test-upc",
"updated_at": Any<String>,
"weight": null,
"width": null,
},
}
`;
exports[`/store/variants GET /store/variants includes default relations 1`] = `
Object {
"variants": Array [
Object {
"allow_backorder": false,
"barcode": "test-barcode",
"calculated_price": null,
"created_at": 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,
"options": Array [
Object {
"created_at": Any<String>,
"deleted_at": null,
"id": "test-variant-option",
"metadata": null,
"option_id": "test-option",
"updated_at": Any<String>,
"value": "Default variant",
"variant_id": "test-variant",
},
],
"origin_country": null,
"original_price": null,
"prices": Array [
Object {
"amount": 100,
"created_at": Any<String>,
"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<String>,
"variant_id": "test-variant",
},
],
"product": Any<Object>,
"product_id": "test-product",
"sku": "test-sku",
"title": "Test variant",
"upc": "test-upc",
"updated_at": Any<String>,
"weight": null,
"width": null,
},
],
}
`;
@@ -102,7 +102,7 @@ Object {
], ],
"origin_country": null, "origin_country": null,
"profile_id": StringMatching /\\^sp_\\*/, "profile_id": StringMatching /\\^sp_\\*/,
"status": "draft", "status": "published",
"subtitle": null, "subtitle": null,
"tags": Array [ "tags": Array [
Object { Object {
@@ -130,6 +130,7 @@ Object {
Object { Object {
"allow_backorder": false, "allow_backorder": false,
"barcode": "test-barcode", "barcode": "test-barcode",
"calculated_price": null,
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"ean": "test-ean", "ean": "test-ean",
@@ -155,6 +156,7 @@ Object {
}, },
], ],
"origin_country": null, "origin_country": null,
"original_price": null,
"prices": Array [ "prices": Array [
Object { Object {
"amount": 100, "amount": 100,
@@ -164,11 +166,37 @@ Object {
"id": "test-price", "id": "test-price",
"max_quantity": null, "max_quantity": null,
"min_quantity": null, "min_quantity": null,
"price_list": null,
"price_list_id": null, "price_list_id": null,
"region_id": null, "region_id": null,
"updated_at": Any<String>, "updated_at": Any<String>,
"variant_id": "test-variant", "variant_id": "test-variant",
}, },
Object {
"amount": 80,
"created_at": Any<String>,
"currency_code": "usd",
"deleted_at": null,
"id": "test-price-discount",
"max_quantity": null,
"min_quantity": null,
"price_list": Object {
"created_at": Any<String>,
"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<String>,
},
"price_list_id": "pl",
"region_id": null,
"updated_at": Any<String>,
"variant_id": "test-variant",
},
], ],
"product_id": "test-product", "product_id": "test-product",
"sku": "test-sku", "sku": "test-sku",
@@ -181,6 +209,7 @@ Object {
Object { Object {
"allow_backorder": false, "allow_backorder": false,
"barcode": null, "barcode": null,
"calculated_price": null,
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"ean": "test-ean2", "ean": "test-ean2",
@@ -206,6 +235,7 @@ Object {
}, },
], ],
"origin_country": null, "origin_country": null,
"original_price": null,
"prices": Array [ "prices": Array [
Object { Object {
"amount": 100, "amount": 100,
@@ -215,11 +245,37 @@ Object {
"id": "test-price2", "id": "test-price2",
"max_quantity": null, "max_quantity": null,
"min_quantity": null, "min_quantity": null,
"price_list": null,
"price_list_id": null, "price_list_id": null,
"region_id": null, "region_id": null,
"updated_at": Any<String>, "updated_at": Any<String>,
"variant_id": "test-variant_2", "variant_id": "test-variant_2",
}, },
Object {
"amount": 80,
"created_at": Any<String>,
"currency_code": "usd",
"deleted_at": null,
"id": "test-price2-discount",
"max_quantity": null,
"min_quantity": null,
"price_list": Object {
"created_at": Any<String>,
"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<String>,
},
"price_list_id": "pl",
"region_id": null,
"updated_at": Any<String>,
"variant_id": "test-variant_2",
},
], ],
"product_id": "test-product", "product_id": "test-product",
"sku": "test-sku2", "sku": "test-sku2",
@@ -232,6 +288,7 @@ Object {
Object { Object {
"allow_backorder": false, "allow_backorder": false,
"barcode": "test-barcode 1", "barcode": "test-barcode 1",
"calculated_price": null,
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"ean": "test-ean1", "ean": "test-ean1",
@@ -257,6 +314,7 @@ Object {
}, },
], ],
"origin_country": null, "origin_country": null,
"original_price": null,
"prices": Array [ "prices": Array [
Object { Object {
"amount": 100, "amount": 100,
@@ -266,11 +324,37 @@ Object {
"id": "test-price1", "id": "test-price1",
"max_quantity": null, "max_quantity": null,
"min_quantity": null, "min_quantity": null,
"price_list": null,
"price_list_id": null, "price_list_id": null,
"region_id": null, "region_id": null,
"updated_at": Any<String>, "updated_at": Any<String>,
"variant_id": "test-variant_1", "variant_id": "test-variant_1",
}, },
Object {
"amount": 80,
"created_at": Any<String>,
"currency_code": "usd",
"deleted_at": null,
"id": "test-price1-discount",
"max_quantity": null,
"min_quantity": null,
"price_list": Object {
"created_at": Any<String>,
"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<String>,
},
"price_list_id": "pl",
"region_id": null,
"updated_at": Any<String>,
"variant_id": "test-variant_1",
},
], ],
"product_id": "test-product", "product_id": "test-product",
"sku": "test-sku1", "sku": "test-sku1",
@@ -300,6 +384,7 @@ Object {
Object { Object {
"allow_backorder": false, "allow_backorder": false,
"barcode": null, "barcode": null,
"calculated_price": null,
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"ean": null, "ean": null,
@@ -313,6 +398,7 @@ Object {
"metadata": null, "metadata": null,
"mid_code": null, "mid_code": null,
"origin_country": null, "origin_country": null,
"original_price": null,
"prices": Array [ "prices": Array [
Object { Object {
"amount": 100, "amount": 100,
@@ -322,6 +408,7 @@ Object {
"id": Any<String>, "id": Any<String>,
"max_quantity": null, "max_quantity": null,
"min_quantity": null, "min_quantity": null,
"price_list": null,
"price_list_id": null, "price_list_id": null,
"region_id": null, "region_id": null,
"updated_at": Any<String>, "updated_at": Any<String>,
+204 -14
View File
@@ -5,6 +5,8 @@ const {
GiftCard, GiftCard,
Cart, Cart,
CustomShippingOption, CustomShippingOption,
PriceList,
MoneyAmount,
} = require("@medusajs/medusa") } = require("@medusajs/medusa")
const setupServer = require("../../../helpers/setup-server") const setupServer = require("../../../helpers/setup-server")
@@ -90,20 +92,51 @@ describe("/store/carts", () => {
it("creates a cart with items", async () => { it("creates a cart with items", async () => {
await productSeeder(dbConnection) await productSeeder(dbConnection)
const yesterday = ((today) =>
new Date(today.setDate(today.getDate() - 1)))(new Date())
const tomorrow = ((today) =>
new Date(today.setDate(today.getDate() + 1)))(new Date())
const priceList1 = await dbConnection.manager.create(PriceList, {
id: "pl_current",
name: "Past winter sale",
description: "Winter sale for key accounts.",
type: "sale",
status: "active",
starts_at: yesterday,
ends_at: tomorrow,
})
await dbConnection.manager.save(priceList1)
const ma_sale_1 = dbConnection.manager.create(MoneyAmount, {
variant_id: "test-variant-sale",
currency_code: "usd",
amount: 800,
price_list_id: "pl_current",
})
await dbConnection.manager.save(ma_sale_1)
const api = useApi() const api = useApi()
const response = await api.post("/store/carts", { const response = await api
items: [ .post("/store/carts", {
{ items: [
variant_id: "test-variant_1", {
quantity: 1, variant_id: "test-variant_1",
}, quantity: 1,
{ },
variant_id: "test-variant_2", {
quantity: 2, variant_id: "test-variant-sale",
}, quantity: 2,
], },
}) ],
})
.catch((err) => console.log(err))
response.data.cart.items.sort((a, b) => a.quantity - b.quantity)
expect(response.status).toEqual(200) expect(response.status).toEqual(200)
expect(response.data.cart.items).toEqual([ expect(response.data.cart.items).toEqual([
@@ -112,8 +145,9 @@ describe("/store/carts", () => {
quantity: 1, quantity: 1,
}), }),
expect.objectContaining({ expect.objectContaining({
variant_id: "test-variant_2", variant_id: "test-variant-sale",
quantity: 2, quantity: 2,
unit_price: 800,
}), }),
]) ])
@@ -155,6 +189,122 @@ describe("/store/carts", () => {
}) })
}) })
describe("POST /store/carts/:id/line-items", () => {
beforeEach(async () => {
try {
await cartSeeder(dbConnection)
await swapSeeder(dbConnection)
} catch (err) {
console.log(err)
throw err
}
})
afterEach(async () => {
await doAfterEach()
})
it("adds line item to cart", async () => {
const api = useApi()
// Add standard line item to cart
const response = await api
.post(
"/store/carts/test-cart/line-items",
{
variant_id: "test-variant-quantity",
quantity: 1,
},
{ withCredentials: true }
)
.catch((err) => console.log(err))
expect(response.data.cart.items).toEqual([
expect.objectContaining({
cart_id: "test-cart",
unit_price: 1000,
variant_id: "test-variant-quantity",
quantity: 1,
}),
])
})
it("adds line item to cart time limited sale", async () => {
const api = useApi()
// Add standard line item to cart
const response = await api
.post(
"/store/carts/test-cart/line-items",
{
variant_id: "test-variant-sale",
quantity: 1,
},
{ withCredentials: true }
)
.catch((err) => console.log(err))
expect(response.data.cart.items).toEqual([
expect.objectContaining({
cart_id: "test-cart",
unit_price: 800,
variant_id: "test-variant-sale",
quantity: 1,
}),
])
})
it("adds line item with quantity to cart with quantity discount", async () => {
const api = useApi()
// Add standard line item to cart
const response = await api
.post(
"/store/carts/test-cart/line-items",
{
variant_id: "test-variant-quantity",
quantity: 90,
},
{ withCredentials: true }
)
.catch((err) => console.log(err))
expect(response.data.cart.items).toEqual([
expect.objectContaining({
cart_id: "test-cart",
unit_price: 800,
variant_id: "test-variant-quantity",
quantity: 90,
}),
])
})
it("adds line item with quantity to cart with quantity discount no ceiling", async () => {
const api = useApi()
// Add standard line item to cart
const response = await api
.post(
"/store/carts/test-cart/line-items",
{
variant_id: "test-variant-quantity",
quantity: 900,
},
{ withCredentials: true }
)
.catch((err) => console.log(err))
expect(response.data.cart.items).toEqual([
expect.objectContaining({
cart_id: "test-cart",
unit_price: 700,
variant_id: "test-variant-quantity",
quantity: 900,
}),
])
})
})
describe("POST /store/carts/:id", () => { describe("POST /store/carts/:id", () => {
beforeEach(async () => { beforeEach(async () => {
try { try {
@@ -270,6 +420,46 @@ describe("/store/carts", () => {
expect(response.status).toEqual(200) expect(response.status).toEqual(200)
}) })
it("updates prices when cart customer id is updated", async () => {
const api = useApi()
const beforeUpdate = await api
.get(`/store/carts/test-cart-3`)
.catch((error) => console.log(error))
expect(beforeUpdate.data.cart.items[0].unit_price).toEqual(8000)
const response = await api
.post("/store/carts/test-cart-3", {
customer_id: "test-customer-2",
})
.catch((error) => console.log(error))
expect(response.status).toEqual(200)
expect(response.data.cart.items[0].unit_price).toEqual(500)
})
it("updates prices when cart region id is updated", async () => {
const api = useApi()
const beforeUpdate = await api
.get(`/store/carts/test-cart-3`)
.catch((error) => console.log(error))
expect(beforeUpdate.data.cart.items[0].unit_price).toEqual(8000)
expect(beforeUpdate.data.cart.region_id).toEqual("test-region")
const response = await api
.post("/store/carts/test-cart-3", {
region_id: "test-region-multiple",
})
.catch((error) => console.log(error))
expect(response.status).toEqual(200)
expect(response.data.cart.region_id).toEqual("test-region-multiple")
expect(response.data.cart.items[0].unit_price).toEqual(700)
})
it("updates address using string id", async () => { it("updates address using string id", async () => {
const api = useApi() const api = useApi()
@@ -377,7 +567,7 @@ describe("/store/carts", () => {
await api.post(`/store/carts/test-cart-2/complete-cart`) await api.post(`/store/carts/test-cart-2/complete-cart`)
} catch (error) { } catch (error) {
expect(error.response.data).toMatchSnapshot({ expect(error.response.data).toMatchSnapshot({
code: "not_allowed", type: "not_allowed",
message: "Cart has already been completed", message: "Cart has already been completed",
code: "cart_incompatible_state", code: "cart_incompatible_state",
}) })
@@ -1,3 +1,4 @@
const { ProductVariant } = require("@medusajs/medusa")
const path = require("path") const path = require("path")
const setupServer = require("../../../helpers/setup-server") const setupServer = require("../../../helpers/setup-server")
const { useApi } = require("../../../helpers/use-api") const { useApi } = require("../../../helpers/use-api")
@@ -5,6 +6,7 @@ const { initDb, useDb } = require("../../../helpers/use-db")
const { simpleProductFactory } = require("../../factories") const { simpleProductFactory } = require("../../factories")
const productSeeder = require("../../helpers/product-seeder") const productSeeder = require("../../helpers/product-seeder")
const storeProductSeeder = require("../../helpers/store-product-seeder")
jest.setTimeout(30000) jest.setTimeout(30000)
describe("/store/variants", () => { describe("/store/variants", () => {
let medusaProcess let medusaProcess
@@ -22,46 +24,100 @@ describe("/store/variants", () => {
medusaProcess.kill() medusaProcess.kill()
}) })
beforeEach(async () => { describe("GET /store/variants", () => {
try { beforeEach(async () => {
await productSeeder(dbConnection) try {
await productSeeder(dbConnection)
await simpleProductFactory( await simpleProductFactory(
dbConnection, dbConnection,
{ {
title: "prod", title: "prod",
variants: [ variants: [
{ {
title: "test1", title: "test1",
inventory_quantity: 10, inventory_quantity: 10,
}, },
{ {
title: "test2", title: "test2",
inventory_quantity: 12, inventory_quantity: 12,
}, },
], ],
}, },
100 100
) )
} catch (err) { } catch (err) {
console.log(err) console.log(err)
throw err throw err
} }
}) })
afterEach(async () => { afterEach(async () => {
const db = useDb() const db = useDb()
await db.teardown() await db.teardown()
}) })
it("includes default relations", async () => { it("includes default relations", async () => {
const api = useApi() const api = useApi()
const response = await api.get("/store/variants?ids=test-variant") const response = await api.get("/store/variants?ids=test-variant")
expect(response.data).toMatchSnapshot({ expect(response.data).toMatchSnapshot({
variants: [ 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,
variant_id: "test-variant",
},
],
product: expect.any(Object),
},
],
})
})
it("/test-variant", async () => {
const api = useApi()
const response = await api.get("/store/variants/test-variant")
expect(response.data).toMatchSnapshot({
variant: {
allow_backorder: false, allow_backorder: false,
barcode: "test-barcode", barcode: "test-barcode",
created_at: expect.any(String), created_at: expect.any(String),
@@ -106,103 +162,230 @@ describe("/store/variants", () => {
}, },
], ],
product: expect.any(Object), product: expect.any(Object),
options: [
{ created_at: expect.any(String), updated_at: expect.any(String) },
],
}, },
], })
}) })
}) })
it("lists by title", async () => { describe("GET /store/variants advanced pricing", () => {
const api = useApi() beforeEach(async () => {
try {
await storeProductSeeder(dbConnection)
const response = await api.get( await simpleProductFactory(
"/store/variants?title[]=test1&title[]=test2&inventory_quantity[gt]=10" dbConnection,
) {
expect(response.data).toMatchSnapshot({ title: "prod",
variants: [ variants: [
{ {
id: expect.any(String), title: "test1",
title: "test2", inventory_quantity: 10,
},
{
title: "test2",
inventory_quantity: 12,
},
],
},
100
)
} catch (err) {
console.log(err)
throw err
}
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("/test-variant with currency code", async () => {
const api = useApi()
const response = await api.get(
"/store/variants/test-variant?currency_code=usd"
)
expect(response.data).toMatchSnapshot({
variant: {
allow_backorder: false,
barcode: "test-barcode",
created_at: expect.any(String), 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), updated_at: expect.any(String),
weight: null,
width: null,
options: [ options: [
{ {
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
id: expect.any(String),
option_id: expect.any(String),
variant_id: expect.any(String),
}, },
], ],
prices: [ prices: [
{ {
id: expect.any(String), created_at: expect.any(String),
variant_id: 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),
},
})
})
it("sets additional prices correctly when querying a variant", async () => {
const api = useApi()
const response = await api
.get("/store/variants/test-variant?cart_id=test-cart")
.catch((error) => console.log(error))
expect(response.data).toMatchSnapshot({
variant: {
id: "test-variant",
inventory_quantity: 10,
title: "Test variant",
sku: "test-sku",
ean: "test-ean",
upc: "test-upc",
barcode: "test-barcode",
product_id: "test-product",
allow_backorder: false,
created_at: expect.any(String),
deleted_at: null,
height: null,
hs_code: null,
length: null,
manage_inventory: true,
material: null,
metadata: null,
mid_code: null,
origin_country: null,
updated_at: expect.any(String),
weight: null,
width: null,
options: [
{
id: "test-variant-option",
value: "Default variant",
option_id: "test-option",
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
}, },
], ],
original_price: 100,
calculated_price: 80,
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,
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), product: expect.any(Object),
product_id: expect.any(String),
}, },
], })
}) })
})
it("/test-variant", async () => { it("lists by title", async () => {
const api = useApi() const api = useApi()
const response = await api.get("/store/variants/test-variant") const response = await api
.get(
"/store/variants?title[]=test1&title[]=test2&inventory_quantity[gt]=10"
)
.catch((err) => console.log(err))
expect(response.data).toMatchSnapshot({ expect(response.data).toMatchSnapshot({
variant: { 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: [
{ {
id: expect.any(String),
title: "test2",
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
options: [
{
created_at: expect.any(String),
updated_at: expect.any(String),
id: expect.any(String),
option_id: expect.any(String),
variant_id: expect.any(String),
},
],
prices: [
{
id: expect.any(String),
variant_id: expect.any(String),
created_at: expect.any(String),
updated_at: expect.any(String),
},
],
product: expect.any(Object),
product_id: 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",
},
],
product: expect.any(Object),
},
}) })
}) })
}) })
+178 -16
View File
@@ -1,4 +1,3 @@
const { Product } = require("@medusajs/medusa")
const path = require("path") const path = require("path")
const setupServer = require("../../../helpers/setup-server") const setupServer = require("../../../helpers/setup-server")
const { useApi } = require("../../../helpers/use-api") const { useApi } = require("../../../helpers/use-api")
@@ -201,17 +200,26 @@ describe("/store/products", () => {
}) })
expect(response.status).toEqual(200) expect(response.status).toEqual(200)
expect(response.data.products.length).toEqual(5)
expect(response.data.products).toEqual([ expect(response.data.products).toEqual([
expect.objectContaining({ expect.objectContaining({
id: "giftcard", id: "test-product1",
collection_id: "test-collection",
}),
expect.objectContaining({
id: "test-product",
collection_id: "test-collection",
}),
expect.objectContaining({
id: "test-product_filtering_2",
collection_id: "test-collection2",
}), }),
expect.objectContaining({ expect.objectContaining({
id: "test-product_filtering_1", id: "test-product_filtering_1",
collection_id: "test-collection1", collection_id: "test-collection1",
}), }),
expect.objectContaining({ expect.objectContaining({
id: "test-product_filtering_2", id: "giftcard",
collection_id: "test-collection2",
}), }),
]) ])
@@ -223,12 +231,111 @@ describe("/store/products", () => {
}) })
}) })
describe("list params", () => {
beforeEach(async () => {
try {
await productSeeder(dbConnection)
await adminSeeder(dbConnection)
} catch (err) {
console.log(err)
throw err
}
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("Includes Additional prices when queried with a cart id", async () => {
const api = useApi()
const response = await api
.get("/store/products?cart_id=test-cart")
.catch((err) => {
console.log(err)
})
expect(response.data.products).toEqual([
expect.objectContaining({
id: "test-product1",
collection_id: "test-collection",
}),
expect.objectContaining({
id: "test-product",
collection_id: "test-collection",
variants: [
expect.objectContaining({
original_price: 100,
calculated_price: 80,
prices: [
expect.objectContaining({
id: "test-price",
currency_code: "usd",
amount: 100,
}),
expect.objectContaining({
id: "test-price-discount",
currency_code: "usd",
amount: 80,
}),
],
}),
expect.objectContaining({
original_price: 100,
calculated_price: 80,
prices: [
expect.objectContaining({
id: "test-price2",
currency_code: "usd",
amount: 100,
}),
expect.objectContaining({
id: "test-price2-discount",
currency_code: "usd",
amount: 80,
}),
],
}),
expect.objectContaining({
original_price: 100,
calculated_price: 80,
prices: [
expect.objectContaining({
id: "test-price1",
currency_code: "usd",
amount: 100,
}),
expect.objectContaining({
id: "test-price1-discount",
currency_code: "usd",
amount: 80,
}),
],
}),
],
}),
expect.objectContaining({
id: "test-product_filtering_2",
collection_id: "test-collection2",
}),
expect.objectContaining({
id: "test-product_filtering_1",
collection_id: "test-collection1",
}),
expect.objectContaining({
id: "giftcard",
}),
])
})
})
describe("list params", () => { describe("list params", () => {
beforeEach(async () => { beforeEach(async () => {
try { try {
await adminSeeder(dbConnection) await adminSeeder(dbConnection)
const p1 = await simpleProductFactory( await simpleProductFactory(
dbConnection, dbConnection,
{ {
title: "testprod", title: "testprod",
@@ -238,7 +345,7 @@ describe("/store/products", () => {
11 11
) )
const p2 = await simpleProductFactory( await simpleProductFactory(
dbConnection, dbConnection,
{ {
title: "testprod3", title: "testprod3",
@@ -332,6 +439,8 @@ describe("/store/products", () => {
height: null, height: null,
hs_code: null, hs_code: null,
origin_country: null, origin_country: null,
calculated_price: null,
original_price: null,
barcode: "test-barcode", barcode: "test-barcode",
product_id: "test-product", product_id: "test-product",
created_at: expect.any(String), created_at: expect.any(String),
@@ -349,13 +458,30 @@ describe("/store/products", () => {
amount: 100, amount: 100,
currency_code: "usd", currency_code: "usd",
deleted_at: null, deleted_at: null,
id: "test-price",
region_id: null,
min_quantity: null, min_quantity: null,
max_quantity: null, max_quantity: null,
price_list_id: null, price_list_id: null,
id: "test-price",
region_id: null,
variant_id: "test-variant", variant_id: "test-variant",
}, },
{
id: "test-price-discount",
created_at: expect.any(String),
updated_at: expect.any(String),
amount: 80,
currency_code: "usd",
price_list_id: "pl",
deleted_at: null,
region_id: null,
variant_id: "test-variant",
price_list: {
id: "pl",
type: "sale",
created_at: expect.any(String),
updated_at: expect.any(String),
},
},
], ],
}, },
{ {
@@ -375,6 +501,8 @@ describe("/store/products", () => {
hs_code: null, hs_code: null,
origin_country: null, origin_country: null,
barcode: null, barcode: null,
calculated_price: null,
original_price: null,
product_id: "test-product", product_id: "test-product",
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
@@ -386,18 +514,33 @@ describe("/store/products", () => {
], ],
prices: [ prices: [
{ {
id: "test-price2",
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
amount: 100, amount: 100,
currency_code: "usd", currency_code: "usd",
deleted_at: null,
id: "test-price2",
region_id: null,
min_quantity: null,
max_quantity: null,
price_list_id: null, price_list_id: null,
deleted_at: null,
region_id: null,
variant_id: "test-variant_2", variant_id: "test-variant_2",
}, },
{
id: "test-price2-discount",
created_at: expect.any(String),
updated_at: expect.any(String),
amount: 80,
currency_code: "usd",
price_list_id: "pl",
deleted_at: null,
region_id: null,
variant_id: "test-variant_2",
price_list: {
id: "pl",
type: "sale",
created_at: expect.any(String),
updated_at: expect.any(String),
},
},
], ],
}, },
{ {
@@ -416,6 +559,8 @@ describe("/store/products", () => {
height: null, height: null,
hs_code: null, hs_code: null,
origin_country: null, origin_country: null,
calculated_price: null,
original_price: null,
barcode: "test-barcode 1", barcode: "test-barcode 1",
product_id: "test-product", product_id: "test-product",
created_at: expect.any(String), created_at: expect.any(String),
@@ -428,18 +573,35 @@ describe("/store/products", () => {
], ],
prices: [ prices: [
{ {
id: "test-price1",
created_at: expect.any(String), created_at: expect.any(String),
updated_at: expect.any(String), updated_at: expect.any(String),
amount: 100, amount: 100,
currency_code: "usd", currency_code: "usd",
deleted_at: null,
id: "test-price1",
region_id: null,
min_quantity: null, min_quantity: null,
max_quantity: null, max_quantity: null,
price_list_id: null, price_list_id: null,
deleted_at: null,
region_id: null,
variant_id: "test-variant_1", variant_id: "test-variant_1",
}, },
{
id: "test-price1-discount",
created_at: expect.any(String),
updated_at: expect.any(String),
amount: 80,
currency_code: "usd",
price_list_id: "pl",
deleted_at: null,
region_id: null,
variant_id: "test-variant_1",
price_list: {
id: "pl",
type: "sale",
created_at: expect.any(String),
updated_at: expect.any(String),
},
},
], ],
}, },
], ],
+175 -3
View File
@@ -14,6 +14,8 @@ const {
LineItem, LineItem,
Payment, Payment,
PaymentSession, PaymentSession,
CustomerGroup,
PriceList,
} = require("@medusajs/medusa") } = require("@medusajs/medusa")
module.exports = async (connection, data = {}) => { module.exports = async (connection, data = {}) => {
@@ -52,6 +54,8 @@ module.exports = async (connection, data = {}) => {
tax_rate: 0, tax_rate: 0,
}) })
await manager.save(r)
// Region with multiple countries // Region with multiple countries
const regionWithMultipleCoutries = manager.create(Region, { const regionWithMultipleCoutries = manager.create(Region, {
id: "test-region-multiple", id: "test-region-multiple",
@@ -240,7 +244,7 @@ module.exports = async (connection, data = {}) => {
is_disabled: false, is_disabled: false,
starts_at: tenDaysAgo, starts_at: tenDaysAgo,
ends_at: tenDaysFromToday, ends_at: tenDaysFromToday,
valid_duration: "P1M", //one month valid_duration: "P1M", // one month
}) })
DynamicDiscount.regions = [r] DynamicDiscount.regions = [r]
@@ -256,11 +260,21 @@ module.exports = async (connection, data = {}) => {
email: "test@email.com", email: "test@email.com",
}) })
await manager.insert(Customer, { const c2 = await manager.create(Customer, {
id: "test-customer-2", id: "test-customer-2",
email: "test-2@email.com", email: "test-2@email.com",
}) })
const cg = await manager.create(CustomerGroup, {
id: "cgroup",
name: "customer group",
})
await manager.save(cg)
c2.groups = [cg]
await manager.save(c2)
await manager.insert(Customer, { await manager.insert(Customer, {
id: "some-customer", id: "some-customer",
email: "some-customer@email.com", email: "some-customer@email.com",
@@ -299,6 +313,28 @@ module.exports = async (connection, data = {}) => {
data: {}, data: {},
}) })
const priceList = await manager.create(PriceList, {
id: "pl",
name: "VIP winter sale",
description: "Winter sale for VIP customers.",
type: "sale",
status: "active",
})
await manager.save(priceList)
const priceList1 = await manager.create(PriceList, {
id: "pl_current",
name: "Past winter sale",
description: "Winter sale for key accounts.",
type: "sale",
status: "active",
starts_at: tenDaysAgo,
ends_at: tenDaysFromToday,
})
await manager.save(priceList1)
await manager.insert(Product, { await manager.insert(Product, {
id: "giftcard-product", id: "giftcard-product",
title: "Giftcard", title: "Giftcard",
@@ -328,6 +364,32 @@ module.exports = async (connection, data = {}) => {
options: [{ id: "test-option", title: "Size" }], options: [{ id: "test-option", title: "Size" }],
}) })
await manager.insert(ProductVariant, {
id: "test-variant-quantity",
title: "test variant",
product_id: "test-product",
inventory_quantity: 1000,
options: [
{
option_id: "test-option",
value: "Size",
},
],
})
await manager.insert(ProductVariant, {
id: "test-variant-sale",
title: "test variant",
product_id: "test-product",
inventory_quantity: 1000,
options: [
{
option_id: "test-option",
value: "Size",
},
],
})
await manager.insert(ProductVariant, { await manager.insert(ProductVariant, {
id: "test-variant", id: "test-variant",
title: "test variant", title: "test variant",
@@ -357,14 +419,60 @@ module.exports = async (connection, data = {}) => {
const ma = manager.create(MoneyAmount, { const ma = manager.create(MoneyAmount, {
variant_id: "test-variant", variant_id: "test-variant",
currency_code: "usd", currency_code: "usd",
type: "default",
amount: 1000, amount: 1000,
}) })
await manager.save(ma) await manager.save(ma)
const ma_sale = manager.create(MoneyAmount, {
variant_id: "test-variant-sale",
currency_code: "usd",
amount: 1000,
})
await manager.save(ma_sale)
const ma_sale_1 = manager.create(MoneyAmount, {
variant_id: "test-variant-sale",
currency_code: "usd",
amount: 800,
price_list_id: "pl_current",
})
await manager.save(ma_sale_1)
const ma_quantity = manager.create(MoneyAmount, {
variant_id: "test-variant-quantity",
currency_code: "usd",
type: "default",
amount: 1000,
})
await manager.save(ma_quantity)
const ma_quantity_1 = manager.create(MoneyAmount, {
variant_id: "test-variant-quantity",
currency_code: "usd",
type: "sale",
min_quantity: 10,
max_quantity: 100,
amount: 800,
})
await manager.save(ma_quantity_1)
const ma_quantity_2 = manager.create(MoneyAmount, {
variant_id: "test-variant-quantity",
currency_code: "usd",
type: "sale",
min_quantity: 100,
amount: 700,
})
await manager.save(ma_quantity_2)
const ma2 = manager.create(MoneyAmount, { const ma2 = manager.create(MoneyAmount, {
variant_id: "test-variant-2", variant_id: "test-variant-2",
currency_code: "usd", currency_code: "usd",
type: "default",
amount: 8000, amount: 8000,
}) })
@@ -373,6 +481,7 @@ module.exports = async (connection, data = {}) => {
const ma3 = manager.create(MoneyAmount, { const ma3 = manager.create(MoneyAmount, {
variant_id: "giftcard-denom", variant_id: "giftcard-denom",
currency_code: "usd", currency_code: "usd",
type: "default",
amount: 1000, amount: 1000,
}) })
@@ -533,4 +642,67 @@ module.exports = async (connection, data = {}) => {
cart_id: "test-cart-3", cart_id: "test-cart-3",
}) })
await manager.save(li2) await manager.save(li2)
const cart4 = manager.create(Cart, {
id: "test-cart-3",
email: "some-customer@email.com",
shipping_address: {
id: "test-shipping-address",
first_name: "lebron",
country_code: "us",
},
region_id: "test-region",
currency_code: "usd",
completed_at: null,
items: [],
})
await manager.save(cart4)
await manager.insert(ProductVariant, {
id: "test-variant-sale-cg",
title: "test variant",
product_id: "test-product",
inventory_quantity: 1000,
options: [
{
option_id: "test-option",
value: "Size",
},
],
})
const ma_cg = manager.create(MoneyAmount, {
variant_id: "test-variant-sale-cg",
currency_code: "usd",
amount: 1000,
})
await manager.save(ma_cg)
const ma_sale_cg = manager.create(MoneyAmount, {
variant_id: "test-variant-sale-cg",
currency_code: "usd",
price_list_id: "pl",
amount: 500,
})
await manager.save(ma_sale_cg)
const ma_sale_cg_new_region = manager.create(MoneyAmount, {
variant_id: "test-variant-sale-cg",
region_id: "test-region-multiple",
currency_code: "eur",
amount: 700,
})
await manager.save(ma_sale_cg_new_region)
const li3 = await manager.create(LineItem, {
id: "test-item3",
title: "Line Item",
description: "Line Item Desc",
thumbnail: "https://test.js/1234",
unit_price: 8000,
quantity: 1,
variant_id: "test-variant-sale-cg",
cart_id: "test-cart-3",
})
await manager.save(li3)
} }
@@ -0,0 +1,883 @@
const {
Customer,
CustomerGroup,
ShippingProfile,
Product,
ProductVariant,
ProductCollection,
ProductOption,
Region,
Cart,
PriceList,
} = require("@medusajs/medusa")
module.exports = async (connection, data = {}) => {
const yesterday = ((today) => new Date(today.setDate(today.getDate() - 1)))(
new Date()
)
const tomorrow = ((today) => new Date(today.setDate(today.getDate() + 1)))(
new Date()
)
const tenDaysAgo = ((today) => new Date(today.setDate(today.getDate() - 10)))(
new Date()
)
const tenDaysFromToday = ((today) =>
new Date(today.setDate(today.getDate() + 10)))(new Date())
const manager = connection.manager
const defaultProfile = await manager.findOne(ShippingProfile, {
type: "default",
})
await manager.insert(Region, {
id: "test-region",
name: "Test Region",
currency_code: "usd",
tax_rate: 0,
})
await manager.insert(Region, {
id: "test-region-1",
name: "Test Region no prices",
currency_code: "usd",
tax_rate: 0,
})
await manager.insert(Region, {
id: "test-region-2",
name: "Test Region no prices",
currency_code: "dkk",
tax_rate: 0,
})
const coll = manager.create(ProductCollection, {
id: "test-collection",
handle: "test-collection",
title: "Test collection",
})
await manager.save(coll)
const customer5 = await manager.create(Customer, {
id: "test-customer-5",
email: "test5@email.com",
first_name: "John",
last_name: "Deere",
password_hash:
"c2NyeXB0AAEAAAABAAAAAVMdaddoGjwU1TafDLLlBKnOTQga7P2dbrfgf3fB+rCD/cJOMuGzAvRdKutbYkVpuJWTU39P7OpuWNkUVoEETOVLMJafbI8qs8Qx/7jMQXkN", // password matching "test"
has_account: true,
})
await manager.save(customer5)
const customer6 = await manager.create(Customer, {
id: "test-customer-6",
password_hash:
"c2NyeXB0AAEAAAABAAAAAVMdaddoGjwU1TafDLLlBKnOTQga7P2dbrfgf3fB+rCD/cJOMuGzAvRdKutbYkVpuJWTU39P7OpuWNkUVoEETOVLMJafbI8qs8Qx/7jMQXkN", // password matching "test"
email: "test6@email.com",
has_account: true,
})
await manager.save(customer6)
const customer7 = await manager.create(Customer, {
id: "test-customer-7",
password_hash:
"c2NyeXB0AAEAAAABAAAAAVMdaddoGjwU1TafDLLlBKnOTQga7P2dbrfgf3fB+rCD/cJOMuGzAvRdKutbYkVpuJWTU39P7OpuWNkUVoEETOVLMJafbI8qs8Qx/7jMQXkN", // password matching "test"
email: "test7@email.com",
has_account: true,
})
await manager.save(customer7)
const c_group_5 = await manager.create(CustomerGroup, {
id: "test-group-5",
name: "test-group-5",
})
await manager.save(c_group_5)
const c_group_6 = await manager.create(CustomerGroup, {
id: "test-group-6",
name: "test-group-6",
})
await manager.save(c_group_6)
customer5.groups = [c_group_5]
await manager.save(customer5)
customer6.groups = [c_group_6]
await manager.save(customer6)
customer7.groups = [c_group_5, c_group_6]
await manager.save(customer7)
const priceList = await manager.create(PriceList, {
id: "pl",
name: "VIP winter sale",
description: "Winter sale for VIP customers.",
type: "sale",
status: "active",
})
await manager.save(priceList)
const priceList1 = await manager.create(PriceList, {
id: "pl_1",
name: "VIP winter sale",
description: "Winter sale for VIP customers.",
type: "sale",
status: "active",
})
priceList1.customer_groups = [c_group_5]
await manager.save(priceList1)
const priceList2 = await manager.create(PriceList, {
id: "pl_2",
name: "VVIP winter sale",
description: "Winter sale for key accounts.",
type: "sale",
status: "active",
})
priceList2.customer_groups = [c_group_6]
await manager.save(priceList2)
const priceList3 = await manager.create(PriceList, {
id: "pl_expired",
name: "Past winter sale",
description: "Winter sale for key accounts.",
type: "sale",
status: "active",
starts_at: tenDaysAgo,
ends_at: yesterday,
})
await manager.save(priceList3)
const priceList4 = await manager.create(PriceList, {
id: "pl_upcoming",
name: "Past winter sale",
description: "Winter sale for key accounts.",
type: "sale",
status: "active",
starts_at: tomorrow,
ends_at: tenDaysFromToday,
})
await manager.save(priceList4)
const priceList5 = await manager.create(PriceList, {
id: "pl_current",
name: "Past winter sale",
description: "Winter sale for key accounts.",
type: "sale",
status: "active",
starts_at: tenDaysAgo,
ends_at: tenDaysFromToday,
})
await manager.save(priceList5)
const priceList6 = await manager.create(PriceList, {
id: "pl_current_1",
name: "Past winter sale",
description: "Winter sale for key accounts.",
type: "sale",
status: "active",
starts_at: tenDaysAgo,
ends_at: tomorrow,
})
await manager.save(priceList6)
const priceList7 = await manager.create(PriceList, {
id: "pl_upcoming-customer",
name: "Past winter sale",
description: "Winter sale for key accounts.",
type: "sale",
status: "active",
starts_at: tomorrow,
ends_at: tenDaysFromToday,
})
priceList7.customer_groups = [c_group_5]
await manager.save(priceList7)
const priceList8 = await manager.create(PriceList, {
id: "pl_current-customer",
name: "Past winter sale",
description: "Winter sale for key accounts.",
type: "sale",
status: "active",
starts_at: tenDaysAgo,
ends_at: tenDaysFromToday,
})
priceList8.customer_groups = [c_group_5]
await manager.save(priceList8)
const priceList9 = await manager.create(PriceList, {
id: "pl_expired-customer",
name: "Past winter sale",
description: "Winter sale for key accounts.",
type: "sale",
status: "active",
starts_at: tenDaysAgo,
ends_at: yesterday,
})
priceList9.customer_groups = [c_group_5]
await manager.save(priceList9)
const p1 = await manager.create(Product, {
id: "test-product",
handle: "test-product",
title: "Test product",
profile_id: defaultProfile.id,
description: "test-product-description1",
collection_id: "test-collection",
tags: [],
})
await manager.save(p1)
await manager.save(ProductOption, {
id: "test-option",
title: "test-option",
product_id: "test-product",
})
const variant4 = await manager.create(ProductVariant, {
id: "test-variant",
inventory_quantity: 10,
title: "Test variant",
sku: "test-sku",
ean: "test-ean",
status: "published",
upc: "test-upc",
product_id: "test-product",
prices: [
{
id: "test-price",
region_id: "test-region",
currency_code: "usd",
amount: 100,
price_list_id: "pl_1",
},
{
id: "test-price1",
region_id: "test-region",
currency_code: "usd",
amount: 120,
},
{
id: "test-price2",
region_id: "test-region",
currency_code: "usd",
amount: 130,
price_list_id: "pl_2",
},
{
id: "test-price3",
region_id: "test-region",
currency_code: "usd",
amount: 110,
price_list_id: "pl",
},
],
options: [
{
id: "test-variant-option",
value: "Default variant",
option_id: "test-option",
},
],
})
await manager.save(variant4)
const p2 = await manager.create(Product, {
id: "test-product-quantity",
handle: "test-product-quantity",
title: "Test product",
profile_id: defaultProfile.id,
description: "test-product-description1",
collection_id: "test-collection",
tags: [],
})
await manager.save(p2)
await manager.save(ProductOption, {
id: "test-option",
title: "test-option",
product_id: "test-product-quantity",
})
const variant_quantity = await manager.create(ProductVariant, {
id: "test-variant-quantity",
inventory_quantity: 10,
title: "Test variant",
sku: "test-sku-quantity",
ean: "test-ean-quantity",
status: "published",
upc: "test-upc-quantity",
product_id: "test-product-quantity",
prices: [
{
id: "test-price-quantity",
region_id: "test-region",
currency_code: "usd",
amount: 100,
price_list_id: "pl",
min_quantity: 10,
max_quantity: 100,
},
{
id: "test-price1-quantity",
region_id: "test-region",
currency_code: "usd",
amount: 120,
price_list_id: "pl",
min_quantity: 101,
max_quantity: 1000,
},
{
id: "test-price1-quantity-group",
region_id: "test-region",
currency_code: "usd",
amount: 120,
min_quantity: 101,
max_quantity: 1000,
price_list_id: "pl_1",
},
{
id: "test-price2-quantity",
region_id: "test-region",
currency_code: "usd",
amount: 130,
price_list_id: "pl",
max_quantity: 9,
},
{
id: "test-price3-quantity-expired",
region_id: "test-region",
currency_code: "usd",
amount: 120,
min_quantity: 101,
max_quantity: 1000,
price_list_id: "pl_expired",
},
{
id: "test-price3-quantity-future",
region_id: "test-region",
currency_code: "usd",
amount: 120,
min_quantity: 101,
max_quantity: 1000,
price_list_id: "pl_upcoming",
},
{
id: "test-price3-quantity-now",
region_id: "test-region",
currency_code: "usd",
amount: 140,
min_quantity: 101,
max_quantity: 1000,
price_list_id: "pl_current",
},
{
id: "test-price3-quantity-default",
region_id: "test-region",
currency_code: "usd",
amount: 150,
},
],
options: [
{
id: "test-variant-option",
value: "Default variant",
option_id: "test-option",
},
],
})
await manager.save(variant_quantity)
const p3 = await manager.create(Product, {
id: "test-product-sale",
handle: "test-product-sale",
title: "Test product sale",
profile_id: defaultProfile.id,
description: "test-product-description1",
collection_id: "test-collection",
tags: [],
})
await manager.save(p3)
await manager.save(ProductOption, {
id: "test-option-sale",
title: "test-option sale",
product_id: "test-product-sale",
})
const variant_sale = await manager.create(ProductVariant, {
id: "test-variant-sale",
inventory_quantity: 10,
title: "Test variant",
sku: "test-sku-sale",
ean: "test-ean-sale",
status: "published",
upc: "test-upc-sale",
product_id: "test-product-sale",
prices: [
{
id: "test-price-sale",
region_id: "test-region",
currency_code: "usd",
amount: 100,
price_list_id: "pl_expired",
},
{
id: "test-price1-sale",
region_id: "test-region",
currency_code: "usd",
amount: 120,
price_list_id: "pl_current",
},
{
id: "test-price2-sale",
region_id: "test-region",
currency_code: "usd",
amount: 130,
price_list_id: "pl_upcoming",
},
{
id: "test-price2-sale-default",
region_id: "test-region",
currency_code: "usd",
amount: 150,
},
],
options: [
{
id: "test-variant-option",
value: "Default variant",
option_id: "test-option",
},
],
})
await manager.save(variant_sale)
const p4 = await manager.create(Product, {
id: "test-product-sale-overlap",
handle: "test-product-sale-overlap",
title: "Test product sale",
profile_id: defaultProfile.id,
description: "test-product-description1",
collection_id: "test-collection",
tags: [],
})
await manager.save(p4)
await manager.save(ProductOption, {
id: "test-option-sale-overlap",
title: "test-option sale",
product_id: "test-product-sale-overlap",
})
const variant_sale_overlap = await manager.create(ProductVariant, {
id: "test-variant-sale-overlap",
inventory_quantity: 10,
title: "Test variant",
sku: "test-sku-sale-overlap",
ean: "test-ean-sale-overlap",
status: "published",
upc: "test-upc-sale-overlap",
product_id: "test-product-sale-overlap",
prices: [
{
id: "test-price-sale-overlap-1",
region_id: "test-region",
currency_code: "usd",
amount: 140,
price_list_id: "pl_current_1",
},
{
id: "test-price1-sale-overlap",
region_id: "test-region",
currency_code: "usd",
amount: 120,
price_list_id: "pl_current",
},
{
id: "test-price2-sale-overlap-default",
region_id: "test-region",
currency_code: "usd",
amount: 150,
},
],
options: [
{
id: "test-variant-option-overlap",
value: "Default variant",
option_id: "test-option-sale-overlap",
},
],
})
await manager.save(variant_sale_overlap)
const multiRegionProduct = await manager.create(Product, {
id: "test-product-multi-region",
handle: "test-product-multi-region",
title: "Test product",
profile_id: defaultProfile.id,
description: "test-product-multi-region-description1",
collection_id: "test-collection",
tags: [],
})
await manager.save(multiRegionProduct)
await manager.save(ProductOption, {
id: "test-option-multi-region",
title: "test-option",
product_id: "test-product-multi-region",
})
const variant_multi_region = await manager.create(ProductVariant, {
id: "test-variant-multi-region",
inventory_quantity: 10,
title: "Test variant",
sku: "test-sku-multi-region",
ean: "test-ean-multi-region",
status: "published",
upc: "test-upc-multi-region",
product_id: "test-product-multi-region",
prices: [
{
id: "test-price-region-1",
region_id: "test-region",
currency_code: "usd",
amount: 100,
price_list_id: "pl",
},
{
id: "test-price1-region-1",
region_id: "test-region",
currency_code: "usd",
amount: 120,
},
{
id: "test-price1-region-2",
region_id: "test-region-2",
currency_code: "dkk",
amount: 130,
},
{
id: "test-price3-region-2",
region_id: "test-region-2",
currency_code: "dkk",
price_list_id: "pl",
amount: 110,
},
],
options: [
{
id: "test-variant-option-multi-region",
value: "Default variant",
option_id: "test-option-multi-region",
},
],
})
await manager.save(variant_multi_region)
const p5 = await manager.create(Product, {
id: "test-product-quantity-customer",
handle: "test-product-quantity-customer",
title: "Test product",
profile_id: defaultProfile.id,
description: "test-product-description1",
collection_id: "test-collection",
tags: [],
})
await manager.save(p5)
await manager.save(ProductOption, {
id: "test-option-quantity-customer",
title: "test-option",
product_id: "test-product-quantity-customer",
})
const variant_quantity_customer = await manager.create(ProductVariant, {
id: "test-variant-quantity-customer",
inventory_quantity: 10,
title: "Test variant",
sku: "test-sku-quantity-customer",
ean: "test-ean-quantity-customer",
status: "published",
upc: "test-upc-quantity-customer",
product_id: "test-product-quantity-customer",
prices: [
{
id: "test-price-quantity-customer",
region_id: "test-region",
currency_code: "usd",
amount: 100,
price_list_id: "pl",
min_quantity: 10,
max_quantity: 100,
},
{
id: "test-price1-quantity-customer",
region_id: "test-region",
currency_code: "usd",
amount: 120,
price_list_id: "pl",
min_quantity: 101,
max_quantity: 1000,
},
{
id: "test-price2-quantity-customer",
region_id: "test-region",
currency_code: "usd",
amount: 130,
price_list_id: "pl",
max_quantity: 9,
},
{
id: "test-price2-quantity-customer-group",
region_id: "test-region",
currency_code: "usd",
amount: 100,
max_quantity: 9,
price_list_id: "pl_1",
},
{
id: "test-price3-quantity-customer-expired",
region_id: "test-region",
currency_code: "usd",
amount: 120,
min_quantity: 101,
max_quantity: 1000,
price_list_id: "pl_expired",
},
{
id: "test-price3-quantity-customer-future",
region_id: "test-region",
currency_code: "usd",
amount: 120,
min_quantity: 101,
max_quantity: 1000,
price_list_id: "pl_upcoming",
},
{
id: "test-price3-quantity-customer-now",
region_id: "test-region",
currency_code: "usd",
amount: 140,
min_quantity: 101,
max_quantity: 1000,
price_list_id: "pl_current",
},
{
id: "test-price3-quantity-customer-default",
region_id: "test-region",
currency_code: "usd",
amount: 150,
},
],
options: [
{
id: "test-variant-option",
value: "Default variant",
option_id: "test-option-quantity-customer",
},
],
})
await manager.save(variant_quantity_customer)
const p6 = await manager.create(Product, {
id: "test-product-sale-customer",
handle: "test-product-sale-customer",
title: "Test product sale",
profile_id: defaultProfile.id,
description: "test-product-description1",
collection_id: "test-collection",
tags: [],
})
await manager.save(p6)
await manager.save(ProductOption, {
id: "test-option-sale",
title: "test-option sale",
product_id: "test-product-sale-customer",
})
const variant_sale_customer = await manager.create(ProductVariant, {
id: "test-variant-sale-customer",
inventory_quantity: 10,
title: "Test variant",
sku: "test-sku-sale-customer",
ean: "test-ean-sale-customer",
status: "published",
upc: "test-upc-sale-customer",
product_id: "test-product-sale-customer",
prices: [
{
id: "test-price-sale-customer",
region_id: "test-region",
currency_code: "usd",
amount: 120,
price_list_id: "pl_expired-customer",
},
{
id: "test-price1-sale-customer",
region_id: "test-region",
currency_code: "usd",
amount: 100,
price_list_id: "pl_current-customer",
},
{
id: "test-price2-sale-customer",
region_id: "test-region",
currency_code: "usd",
amount: 130,
price_list_id: "pl_upcoming-customer",
},
{
id: "test-price2-sale-customer-default",
region_id: "test-region",
currency_code: "usd",
amount: 150,
type: "default",
},
],
options: [
{
id: "test-variant-option",
value: "Default variant",
option_id: "test-option",
},
],
})
await manager.save(variant_sale_customer)
const p7 = await manager.create(Product, {
id: "test-product-sale-customer-quantity",
handle: "test-product-sale-customer-quantity",
title: "Test product sale",
profile_id: defaultProfile.id,
description: "test-product-description1",
collection_id: "test-collection",
tags: [],
})
await manager.save(p7)
await manager.save(ProductOption, {
id: "test-option-sale",
title: "test-option sale",
product_id: "test-product-sale-customer-quantity",
})
const variant_sale_customer_quantity = await manager.create(ProductVariant, {
id: "test-variant-sale-customer-quantity",
inventory_quantity: 10,
title: "Test variant",
sku: "test-sku-sale-customer-quantity",
ean: "test-ean-sale-customer-quantity",
status: "published",
upc: "test-upc-sale-customer-quantity",
product_id: "test-product-sale-customer-quantity",
prices: [
{
id: "test-price-sale-customer-quantity",
region_id: "test-region",
currency_code: "usd",
amount: 120,
min_quantity: 100,
max_quantity: 1000,
price_list_id: "pl_expired-customer",
},
{
id: "test-price1-sale-customer-quantity-groups",
region_id: "test-region",
currency_code: "usd",
amount: 100,
max_quantity: 99,
price_list_id: "pl_current-customer",
},
{
id: "test-price2-sale-customer-quantity",
region_id: "test-region",
currency_code: "usd",
amount: 130,
min_quantity: 500,
max_quantity: 900,
price_list_id: "pl_upcoming-customer",
},
{
id: "test-price2-sale-customer-quantity-default",
region_id: "test-region",
currency_code: "usd",
amount: 150,
},
{
id: "test-price1-sale-customer-quantity",
region_id: "test-region",
currency_code: "usd",
amount: 110,
max_quantity: 99,
},
],
options: [
{
id: "test-variant-option",
value: "Default variant",
option_id: "test-option",
},
],
})
await manager.save(variant_sale_customer_quantity)
const cart = await manager.create(Cart, {
id: "test-cart",
region_id: "test-region",
currency_code: "usd",
items: [],
})
await manager.save(cart)
const cart_region2 = await manager.create(Cart, {
id: "test-cart-1",
region_id: "test-region-2",
currency_code: "dkk",
items: [],
})
await manager.save(cart_region2)
const cart_region1 = await manager.create(Cart, {
id: "test-cart-2",
region_id: "test-region-1",
currency_code: "usd",
items: [],
})
await manager.save(cart_region1)
}
+45 -14
View File
@@ -17,7 +17,7 @@ module.exports = async (connection, data = {}) => {
type: "default", type: "default",
}) })
const coll = manager.create(ProductCollection, { const coll = await manager.create(ProductCollection, {
id: "test-collection", id: "test-collection",
handle: "test-collection", handle: "test-collection",
title: "Test collection", title: "Test collection",
@@ -25,7 +25,7 @@ module.exports = async (connection, data = {}) => {
await manager.save(coll) await manager.save(coll)
const coll1 = manager.create(ProductCollection, { const coll1 = await manager.create(ProductCollection, {
id: "test-collection1", id: "test-collection1",
handle: "test-collection1", handle: "test-collection1",
title: "Test collection 1", title: "Test collection 1",
@@ -33,7 +33,7 @@ module.exports = async (connection, data = {}) => {
await manager.save(coll1) await manager.save(coll1)
const coll2 = manager.create(ProductCollection, { const coll2 = await manager.create(ProductCollection, {
id: "test-collection2", id: "test-collection2",
handle: "test-collection2", handle: "test-collection2",
title: "Test collection 2", title: "Test collection 2",
@@ -41,28 +41,28 @@ module.exports = async (connection, data = {}) => {
await manager.save(coll2) await manager.save(coll2)
const tag = manager.create(ProductTag, { const tag = await manager.create(ProductTag, {
id: "tag1", id: "tag1",
value: "123", value: "123",
}) })
await manager.save(tag) await manager.save(tag)
const tag3 = manager.create(ProductTag, { const tag3 = await manager.create(ProductTag, {
id: "tag3", id: "tag3",
value: "123", value: "123",
}) })
await manager.save(tag3) await manager.save(tag3)
const tag4 = manager.create(ProductTag, { const tag4 = await manager.create(ProductTag, {
id: "tag4", id: "tag4",
value: "123", value: "123",
}) })
await manager.save(tag4) await manager.save(tag4)
const type = manager.create(ProductType, { const type = await manager.create(ProductType, {
id: "test-type", id: "test-type",
value: "test-type", value: "test-type",
}) })
@@ -83,7 +83,7 @@ module.exports = async (connection, data = {}) => {
tax_rate: 0, tax_rate: 0,
}) })
const p = manager.create(Product, { const p = await manager.create(Product, {
id: "test-product", id: "test-product",
handle: "test-product", handle: "test-product",
title: "Test product", title: "Test product",
@@ -129,6 +129,34 @@ module.exports = async (connection, data = {}) => {
await manager.save(variant1) await manager.save(variant1)
const sale = await manager.create(ProductVariant, {
id: "test-variant-sale",
inventory_quantity: 10,
title: "Test variant",
variant_rank: 3,
sku: "test-sku-sale",
ean: "test-ean-sale",
upc: "test-upc-sale",
barcode: "test-barcode-sale",
product_id: "test-product",
prices: [
{
id: "test-price-sale",
currency_code: "usd",
amount: 1000,
},
],
options: [
{
id: "test-variant-option-sale",
value: "Default variant",
option_id: "test-option",
},
],
})
await manager.save(sale)
const variant2 = await manager.create(ProductVariant, { const variant2 = await manager.create(ProductVariant, {
id: "test-variant_1", id: "test-variant_1",
inventory_quantity: 10, inventory_quantity: 10,
@@ -172,7 +200,7 @@ module.exports = async (connection, data = {}) => {
await manager.save(variant3) await manager.save(variant3)
const p1 = manager.create(Product, { const p1 = await manager.create(Product, {
id: "test-product1", id: "test-product1",
handle: "test-product1", handle: "test-product1",
title: "Test product1", title: "Test product1",
@@ -203,6 +231,7 @@ module.exports = async (connection, data = {}) => {
region_id: "test-region", region_id: "test-region",
currency_code: "usd", currency_code: "usd",
amount: 100, amount: 100,
type: "default",
}, },
], ],
options: [ options: [
@@ -225,7 +254,9 @@ module.exports = async (connection, data = {}) => {
ean: "test-ean4", ean: "test-ean4",
upc: "test-upc4", upc: "test-upc4",
product_id: "test-product1", product_id: "test-product1",
prices: [{ id: "test-price4", currency_code: "usd", amount: 100 }], prices: [
{ id: "test-price4", currency_code: "usd", amount: 100, type: "default" },
],
options: [ options: [
{ {
id: "test-variant-option-4", id: "test-variant-option-4",
@@ -237,7 +268,7 @@ module.exports = async (connection, data = {}) => {
await manager.save(variant5) await manager.save(variant5)
const product1 = manager.create(Product, { const product1 = await manager.create(Product, {
id: "test-product_filtering_1", id: "test-product_filtering_1",
handle: "test-product_filtering_1", handle: "test-product_filtering_1",
title: "Test product filtering 1", title: "Test product filtering 1",
@@ -251,7 +282,7 @@ module.exports = async (connection, data = {}) => {
await manager.save(product1) await manager.save(product1)
const product2 = manager.create(Product, { const product2 = await manager.create(Product, {
id: "test-product_filtering_2", id: "test-product_filtering_2",
handle: "test-product_filtering_2", handle: "test-product_filtering_2",
title: "Test product filtering 2", title: "Test product filtering 2",
@@ -265,7 +296,7 @@ module.exports = async (connection, data = {}) => {
await manager.save(product2) await manager.save(product2)
const product3 = manager.create(Product, { const product3 = await manager.create(Product, {
id: "test-product_filtering_3", id: "test-product_filtering_3",
handle: "test-product_filtering_3", handle: "test-product_filtering_3",
title: "Test product filtering 3", title: "Test product filtering 3",
@@ -279,7 +310,7 @@ module.exports = async (connection, data = {}) => {
await manager.save(product3) await manager.save(product3)
const product4 = manager.create(Product, { const product4 = await manager.create(Product, {
id: "test-product_filtering_4", id: "test-product_filtering_4",
handle: "test-product_filtering_4", handle: "test-product_filtering_4",
title: "Test product filtering 4", title: "Test product filtering 4",
@@ -8,11 +8,45 @@ const {
ShippingProfile, ShippingProfile,
ProductVariant, ProductVariant,
Image, Image,
Cart,
PriceList,
} = require("@medusajs/medusa") } = require("@medusajs/medusa")
module.exports = async (connection, data = {}) => { module.exports = async (connection, data = {}) => {
const manager = connection.manager const manager = connection.manager
const yesterday = ((today) => new Date(today.setDate(today.getDate() - 1)))(
new Date()
)
const tomorrow = ((today) => new Date(today.setDate(today.getDate() + 1)))(
new Date()
)
const tenDaysAgo = ((today) => new Date(today.setDate(today.getDate() - 10)))(
new Date()
)
const priceList = await manager.create(PriceList, {
id: "pl",
name: "VIP winter sale",
description: "Winter sale for VIP customers.",
type: "sale",
status: "active",
})
await manager.save(priceList)
const priceList1 = await manager.create(PriceList, {
id: "pl_expired",
name: "Past winter sale",
description: "Winter sale for key accounts.",
type: "sale",
status: "active",
starts_at: tenDaysAgo,
ends_at: yesterday,
})
await manager.save(priceList1)
const defaultProfile = await manager.findOne(ShippingProfile, { const defaultProfile = await manager.findOne(ShippingProfile, {
type: "default", type: "default",
}) })
@@ -83,12 +117,20 @@ module.exports = async (connection, data = {}) => {
tax_rate: 0, tax_rate: 0,
}) })
await manager.insert(Cart, {
id: "test-cart",
region_id: "test-region",
currency_code: "usd",
items: [],
})
const p = manager.create(Product, { const p = manager.create(Product, {
id: "test-product", id: "test-product",
handle: "test-product", handle: "test-product",
title: "Test product", title: "Test product",
profile_id: defaultProfile.id, profile_id: defaultProfile.id,
description: "test-product-description", description: "test-product-description",
status: "published",
collection_id: "test-collection", collection_id: "test-collection",
type: { id: "test-type", value: "test-type" }, type: { id: "test-type", value: "test-type" },
tags: [ tags: [
@@ -117,7 +159,21 @@ module.exports = async (connection, data = {}) => {
upc: "test-upc", upc: "test-upc",
barcode: "test-barcode", barcode: "test-barcode",
product_id: "test-product", product_id: "test-product",
prices: [{ id: "test-price", currency_code: "usd", amount: 100 }], prices: [
{ id: "test-price", currency_code: "usd", type: "default", amount: 100 },
{
id: "test-price-discount",
currency_code: "usd",
amount: 80,
price_list_id: "pl",
},
{
id: "test-price-discount-expired",
currency_code: "usd",
amount: 70,
price_list_id: "pl_expired",
},
],
options: [ options: [
{ {
id: "test-variant-option", id: "test-variant-option",
@@ -139,7 +195,21 @@ module.exports = async (connection, data = {}) => {
upc: "test-upc1", upc: "test-upc1",
barcode: "test-barcode 1", barcode: "test-barcode 1",
product_id: "test-product", product_id: "test-product",
prices: [{ id: "test-price1", currency_code: "usd", amount: 100 }], prices: [
{ id: "test-price1", currency_code: "usd", type: "default", amount: 100 },
{
id: "test-price1-discount",
currency_code: "usd",
amount: 80,
price_list_id: "pl",
},
{
id: "test-price1-discount-expired",
currency_code: "usd",
amount: 70,
price_list_id: "pl_expired",
},
],
options: [ options: [
{ {
id: "test-variant-option-1", id: "test-variant-option-1",
@@ -160,7 +230,21 @@ module.exports = async (connection, data = {}) => {
ean: "test-ean2", ean: "test-ean2",
upc: "test-upc2", upc: "test-upc2",
product_id: "test-product", product_id: "test-product",
prices: [{ id: "test-price2", currency_code: "usd", amount: 100 }], prices: [
{ id: "test-price2", currency_code: "usd", type: "default", amount: 100 },
{
id: "test-price2-discount",
currency_code: "usd",
amount: 80,
price_list_id: "pl",
},
{
id: "test-price2-discount-expired",
currency_code: "usd",
amount: 70,
price_list_id: "pl_expired",
},
],
options: [ options: [
{ {
id: "test-variant-option-2", id: "test-variant-option-2",
@@ -176,6 +260,7 @@ module.exports = async (connection, data = {}) => {
id: "test-product1", id: "test-product1",
handle: "test-product1", handle: "test-product1",
title: "Test product1", title: "Test product1",
status: "published",
profile_id: defaultProfile.id, profile_id: defaultProfile.id,
description: "test-product-description1", description: "test-product-description1",
collection_id: "test-collection", collection_id: "test-collection",
@@ -74,9 +74,9 @@ export const ProductVariantServiceMock = {
} }
return Promise.resolve(undefined) return Promise.resolve(undefined)
}), }),
getRegionPrice: jest.fn().mockImplementation((variantId, regionId) => { getRegionPrice: jest.fn().mockImplementation((variantId, context) => {
if (variantId === IdMap.getId("test-variant-1")) { if (variantId === IdMap.getId("test-variant-1")) {
if (regionId === IdMap.getId("world")) { if (context.regionId === IdMap.getId("world")) {
return Promise.resolve(10) return Promise.resolve(10)
} else { } else {
return Promise.resolve(20) return Promise.resolve(20)
@@ -58,7 +58,7 @@ class AddOnLineItemService extends BaseService {
let unitPrice = await this.productVariantService_.getRegionPrice( let unitPrice = await this.productVariantService_.getRegionPrice(
variant._id, variant._id,
region._id {regionId: region._id}
) )
const addOnPrices = await Promise.all( const addOnPrices = await Promise.all(
@@ -8,8 +8,10 @@ import {
IsString, IsString,
ValidateNested, ValidateNested,
} from "class-validator" } from "class-validator"
import { MedusaError } from "medusa-core-utils"
import { defaultAdminProductFields, defaultAdminProductRelations } from "." import { defaultAdminProductFields, defaultAdminProductRelations } from "."
import { ProductService, ProductVariantService } from "../../../../services" import { ProductService, ProductVariantService } from "../../../../services"
import { PriceSelectionParams } from "../../../../types/price-selection"
import { ProductVariantPricesUpdateReq } from "../../../../types/product-variant" import { ProductVariantPricesUpdateReq } from "../../../../types/product-variant"
import { validator } from "../../../../utils/validator" import { validator } from "../../../../utils/validator"
@@ -130,6 +132,8 @@ export default async (req, res) => {
req.body req.body
) )
const validatedQueryParams = await validator(PriceSelectionParams, req.query)
const productService: ProductService = req.scope.resolve("productService") const productService: ProductService = req.scope.resolve("productService")
const productVariantService: ProductVariantService = req.scope.resolve( const productVariantService: ProductVariantService = req.scope.resolve(
"productVariantService" "productVariantService"
@@ -140,9 +144,12 @@ export default async (req, res) => {
...validated, ...validated,
}) })
await productVariantService.retrieve(variant_id)
const product = await productService.retrieve(id, { const product = await productService.retrieve(id, {
select: defaultAdminProductFields, select: defaultAdminProductFields,
relations: defaultAdminProductRelations, relations: defaultAdminProductRelations,
...validatedQueryParams,
}) })
res.json({ product }) res.json({ product })
@@ -21,7 +21,13 @@ describe("Get product by id", () => {
expect(ProductServiceMock.retrieve).toHaveBeenCalledTimes(1) expect(ProductServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(ProductServiceMock.retrieve).toHaveBeenCalledWith( expect(ProductServiceMock.retrieve).toHaveBeenCalledWith(
IdMap.getId("product1"), IdMap.getId("product1"),
{ relations: defaultStoreProductsRelations } {
relations: defaultStoreProductsRelations,
cart_id: undefined,
currency_code: undefined,
region_id: undefined,
include_discount_prices: true,
}
) )
}) })
@@ -51,7 +57,13 @@ describe("Get product by id", () => {
it("endpoint called with defaultRelations", () => { it("endpoint called with defaultRelations", () => {
expect(ProductServiceMock.retrieve).toHaveBeenCalledWith( expect(ProductServiceMock.retrieve).toHaveBeenCalledWith(
IdMap.getId("variantsWithPrices"), IdMap.getId("variantsWithPrices"),
{ relations: defaultStoreProductsRelations } {
relations: defaultStoreProductsRelations,
cart_id: undefined,
currency_code: undefined,
region_id: undefined,
include_discount_prices: true,
}
) )
}) })
@@ -19,7 +19,15 @@ describe("GET /store/products", () => {
expect(ProductServiceMock.listAndCount).toHaveBeenCalledTimes(1) expect(ProductServiceMock.listAndCount).toHaveBeenCalledTimes(1)
expect(ProductServiceMock.listAndCount).toHaveBeenCalledWith( expect(ProductServiceMock.listAndCount).toHaveBeenCalledWith(
{ status: ["published"] }, { status: ["published"] },
{ relations: defaultStoreProductsRelations, skip: 0, take: 100 } {
relations: defaultStoreProductsRelations,
skip: 0,
take: 100,
cart_id: undefined,
currency_code: undefined,
region_id: undefined,
include_discount_prices: true,
}
) )
}) })
@@ -42,7 +50,15 @@ describe("GET /store/products", () => {
expect(ProductServiceMock.listAndCount).toHaveBeenCalledTimes(1) expect(ProductServiceMock.listAndCount).toHaveBeenCalledTimes(1)
expect(ProductServiceMock.listAndCount).toHaveBeenCalledWith( expect(ProductServiceMock.listAndCount).toHaveBeenCalledWith(
{ is_giftcard: true, status: ["published"] }, { is_giftcard: true, status: ["published"] },
{ relations: defaultStoreProductsRelations, skip: 0, take: 100 } {
relations: defaultStoreProductsRelations,
skip: 0,
take: 100,
cart_id: undefined,
currency_code: undefined,
region_id: undefined,
include_discount_prices: true,
}
) )
}) })
}) })
@@ -1,5 +1,7 @@
import { defaultStoreProductsRelations } from "." import { defaultStoreProductsRelations } from "."
import { ProductService } from "../../../../services" import { ProductService } from "../../../../services"
import { PriceSelectionParams } from "../../../../types/price-selection"
import { validator } from "../../../../utils/validator"
/** /**
* @oas [get] /products/{id} * @oas [get] /products/{id}
@@ -23,9 +25,18 @@ import { ProductService } from "../../../../services"
export default async (req, res) => { export default async (req, res) => {
const { id } = req.params const { id } = req.params
const validated = await validator(PriceSelectionParams, req.query)
const customer_id = req.user?.customer_id
const productService: ProductService = req.scope.resolve("productService") const productService: ProductService = req.scope.resolve("productService")
const product = await productService.retrieve(id, { const product = await productService.retrieve(id, {
relations: defaultStoreProductsRelations, relations: defaultStoreProductsRelations,
cart_id: validated.cart_id,
customer_id: customer_id,
region_id: validated.region_id,
currency_code: validated.currency_code,
include_discount_prices: true,
}) })
res.json({ product }) res.json({ product })
@@ -11,6 +11,7 @@ import { omit, pickBy } from "lodash"
import { defaultStoreProductsRelations } from "." import { defaultStoreProductsRelations } from "."
import { ProductService } from "../../../../services" import { ProductService } from "../../../../services"
import { DateComparisonOperator } from "../../../../types/common" import { DateComparisonOperator } from "../../../../types/common"
import { PriceSelectionParams } from "../../../../types/price-selection"
import { validator } from "../../../../utils/validator" import { validator } from "../../../../utils/validator"
import { IsType } from "../../../../utils/validators/is-type" import { IsType } from "../../../../utils/validators/is-type"
import { optionalBooleanMapper } from "../../../../utils/validators/is-boolean" import { optionalBooleanMapper } from "../../../../utils/validators/is-boolean"
@@ -68,6 +69,9 @@ export default async (req, res) => {
"expand", "expand",
"limit", "limit",
"offset", "offset",
"cart_id",
"region_id",
"currency_code",
]) ])
// get only published products for store endpoint // get only published products for store endpoint
@@ -92,6 +96,11 @@ export default async (req, res) => {
: defaultStoreProductsRelations, : defaultStoreProductsRelations,
skip: validated.offset, skip: validated.offset,
take: validated.limit, take: validated.limit,
cart_id: validated.cart_id,
region_id: validated.region_id,
currency_code: validated.currency_code,
customer_id: req.user?.customer_id,
include_discount_prices: true,
} }
const [products, count] = await productService.listAndCount( const [products, count] = await productService.listAndCount(
@@ -107,7 +116,7 @@ export default async (req, res) => {
}) })
} }
export class StoreGetProductsPaginationParams { export class StoreGetProductsPaginationParams extends PriceSelectionParams {
@IsString() @IsString()
@IsOptional() @IsOptional()
fields?: string fields?: string
@@ -16,6 +16,10 @@ describe("Get variant by id", () => {
expect(ProductVariantServiceMock.retrieve).toHaveBeenCalledTimes(1) expect(ProductVariantServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(ProductVariantServiceMock.retrieve).toHaveBeenCalledWith("1", { expect(ProductVariantServiceMock.retrieve).toHaveBeenCalledWith("1", {
relations: ["prices", "options"], relations: ["prices", "options"],
cart_id: undefined,
currency_code: undefined,
region_id: undefined,
include_discount_prices: true,
}) })
}) })
@@ -1,5 +1,7 @@
import { defaultStoreVariantRelations } from "." import { defaultStoreVariantRelations } from "."
import ProductVariantService from "../../../../services/product-variant" import ProductVariantService from "../../../../services/product-variant"
import { PriceSelectionParams } from "../../../../types/price-selection"
import { validator } from "../../../../utils/validator"
/** /**
* @oas [get] /variants/{variant_id} * @oas [get] /variants/{variant_id}
@@ -23,12 +25,21 @@ import ProductVariantService from "../../../../services/product-variant"
export default async (req, res) => { export default async (req, res) => {
const { id } = req.params const { id } = req.params
const validated = await validator(PriceSelectionParams, req.query)
const variantService: ProductVariantService = req.scope.resolve( const variantService: ProductVariantService = req.scope.resolve(
"productVariantService" "productVariantService"
) )
const customer_id = req.user?.customer_id
const variant = await variantService.retrieve(id, { const variant = await variantService.retrieve(id, {
relations: defaultStoreVariantRelations, relations: defaultStoreVariantRelations,
cart_id: validated.cart_id,
customer_id: customer_id,
region_id: validated.region_id,
currency_code: validated.currency_code,
include_discount_prices: true,
}) })
res.json({ variant }) res.json({ variant })
@@ -7,6 +7,7 @@ import ProductVariantService from "../../../../services/product-variant"
import { validator } from "../../../../utils/validator" import { validator } from "../../../../utils/validator"
import { IsType } from "../../../../utils/validators/is-type" import { IsType } from "../../../../utils/validators/is-type"
import { NumericalComparisonOperator } from "../../../../types/common" import { NumericalComparisonOperator } from "../../../../types/common"
import { PriceSelectionParams } from "../../../../types/price-selection"
/** /**
* @oas [get] /variants * @oas [get] /variants
@@ -41,12 +42,19 @@ export default async (req, res) => {
expandFields = expand.split(",") expandFields = expand.split(",")
} }
const customer_id = req.user?.customer_id
const listConfig = { const listConfig = {
relations: expandFields.length relations: expandFields.length
? expandFields ? expandFields
: defaultStoreVariantRelations, : defaultStoreVariantRelations,
skip: offset, skip: offset,
take: limit, take: limit,
cart_id: validated.cart_id,
region_id: validated.region_id,
currency_code: validated.currency_code,
customer_id: customer_id,
include_discount_prices: true,
} }
const filterableFields: FilterableProductVariantProps = omit(validated, [ const filterableFields: FilterableProductVariantProps = omit(validated, [
@@ -54,6 +62,9 @@ export default async (req, res) => {
"limit", "limit",
"offset", "offset",
"expand", "expand",
"cart_id",
"region_id",
"currency_code",
]) ])
if (validated.ids) { if (validated.ids) {
@@ -68,7 +79,7 @@ export default async (req, res) => {
res.json({ variants }) res.json({ variants })
} }
export class StoreGetVariantsParams { export class StoreGetVariantsParams extends PriceSelectionParams {
@IsOptional() @IsOptional()
@IsInt() @IsInt()
@Type(() => Number) @Type(() => Number)
@@ -1,6 +1,5 @@
import { EntityManager } from "typeorm" import { EntityManager } from "typeorm"
import { MoneyAmount } from ".." import { MoneyAmount } from ".."
import { MoneyAmountRepository } from "../repositories/money-amount"
import { PriceListType } from "../types/price-list" import { PriceListType } from "../types/price-list"
export interface IPriceSelectionStrategy { export interface IPriceSelectionStrategy {
+5 -6
View File
@@ -8,7 +8,7 @@ import {
JoinColumn, JoinColumn,
ManyToOne, ManyToOne,
PrimaryColumn, PrimaryColumn,
UpdateDateColumn UpdateDateColumn,
} from "typeorm" } from "typeorm"
import { ulid } from "ulid" import { ulid } from "ulid"
import { resolveDbType } from "../utils/db-aware-column" import { resolveDbType } from "../utils/db-aware-column"
@@ -41,11 +41,10 @@ export class MoneyAmount {
@Column({ nullable: true }) @Column({ nullable: true })
price_list_id: string | null price_list_id: string | null
@ManyToOne( @ManyToOne(() => PriceList, (priceList) => priceList.prices, {
() => PriceList, cascade: true,
(priceList) => priceList.prices, onDelete: "CASCADE",
{ cascade: true, onDelete: "CASCADE" } })
)
@JoinColumn({ name: "price_list_id" }) @JoinColumn({ name: "price_list_id" })
price_list: PriceList | null price_list: PriceList | null
+7 -11
View File
@@ -8,7 +8,7 @@ import {
ManyToMany, ManyToMany,
OneToMany, OneToMany,
PrimaryColumn, PrimaryColumn,
UpdateDateColumn UpdateDateColumn,
} from "typeorm" } from "typeorm"
import { ulid } from "ulid" import { ulid } from "ulid"
import { PriceListStatus, PriceListType } from "../types/price-list" import { PriceListStatus, PriceListType } from "../types/price-list"
@@ -53,18 +53,14 @@ export class PriceList {
referencedColumnName: "id", referencedColumnName: "id",
}, },
}) })
@ManyToMany( @ManyToMany(() => CustomerGroup, (cg) => cg.price_lists, {
() => CustomerGroup, onDelete: "CASCADE",
(cg) => cg.price_lists, })
{ onDelete: "CASCADE" }
)
customer_groups: CustomerGroup[] customer_groups: CustomerGroup[]
@OneToMany( @OneToMany(() => MoneyAmount, (moneyAmount) => moneyAmount.price_list, {
() => MoneyAmount, onDelete: "CASCADE",
(moneyAmount) => moneyAmount.price_list, })
{ onDelete: "CASCADE" }
)
prices: MoneyAmount[] prices: MoneyAmount[]
@CreateDateColumn({ type: resolveDbType("timestamptz") }) @CreateDateColumn({ type: resolveDbType("timestamptz") })
@@ -1,4 +1,5 @@
import partition from "lodash/partition" import partition from "lodash/partition"
import { MedusaError } from "medusa-core-utils"
import { import {
Brackets, Brackets,
EntityRepository, EntityRepository,
@@ -130,7 +131,7 @@ export class MoneyAmountRepository extends Repository<MoneyAmount> {
"price_list", "price_list",
"ma.price_list_id = price_list.id " "ma.price_list_id = price_list.id "
) )
.where({ variant_id: variant_id }) // "ma.variant_id = :variant_id", .where({ variant_id: variant_id })
.andWhere("(ma.price_list_id is null or price_list.status = 'active')") .andWhere("(ma.price_list_id is null or price_list.status = 'active')")
.andWhere( .andWhere(
"(price_list is null or price_list.ends_at is null OR price_list.ends_at > :date) ", "(price_list is null or price_list.ends_at is null OR price_list.ends_at > :date) ",
+15 -1
View File
@@ -1040,6 +1040,19 @@ describe("CartService", () => {
return this return this
}, },
} }
const priceSelectionStrat = {
calculateVariantPrice: async (variantId, context) => {
if (variantId === IdMap.getId("fail")) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Money amount for variant with id ${variantId} in region ${context.region_id} does not exist`
)
} else {
return { calculatedPrice: 100 }
}
},
}
const cartService = new CartService({ const cartService = new CartService({
manager: MockManager, manager: MockManager,
paymentProviderService, paymentProviderService,
@@ -1050,6 +1063,7 @@ describe("CartService", () => {
lineItemService, lineItemService,
productVariantService, productVariantService,
eventBusService, eventBusService,
priceSelectionStrategy: priceSelectionStrat,
}) })
beforeEach(() => { beforeEach(() => {
@@ -1316,7 +1330,7 @@ describe("CartService", () => {
jest.clearAllMocks() jest.clearAllMocks()
}) })
let cartService = new CartService({}) const cartService = new CartService({})
it("given a cart with custom shipping options and a shipping option id corresponding to a custom shipping option, then it should return a custom shipping option", async () => { it("given a cart with custom shipping options and a shipping option id corresponding to a custom shipping option, then it should return a custom shipping option", async () => {
const cartCSO = [ const cartCSO = [
@@ -3,7 +3,7 @@ import ProductVariantService from "../product-variant"
const eventBusService = { const eventBusService = {
emit: jest.fn(), emit: jest.fn(),
withTransaction: function() { withTransaction: function () {
return this return this
}, },
} }
@@ -18,9 +18,33 @@ describe("ProductVariantService", () => {
return Promise.resolve({ id: IdMap.getId("ironman") }) return Promise.resolve({ id: IdMap.getId("ironman") })
}, },
}) })
const cartRepository = MockRepository({
findOne: (data) => {
return Promise.resolve({})
},
})
const priceSelectionStrat = {
calculateVariantPrice: (variantId, context) => {
return {
originalPrice: null,
calculatedPrice: null,
prices: [],
}
},
}
const priceSelectionStrategy = {
withTransaction: (manager) => {
return priceSelectionStrat
},
}
const productVariantService = new ProductVariantService({ const productVariantService = new ProductVariantService({
manager: MockManager, manager: MockManager,
productVariantRepository, productVariantRepository,
cartRepository,
priceSelectionStrategy,
}) })
beforeEach(async () => { beforeEach(async () => {
@@ -227,6 +251,29 @@ describe("ProductVariantService", () => {
}) })
describe("update", () => { describe("update", () => {
const cartRepository = MockRepository({
findOne: (data) => {
return Promise.resolve({})
},
})
const priceSelectionStrat = {
calculateVariantPrice: (variantId, context) => {
return {
originalPrice: null,
calculatedPrice: null,
calculatedPriceType: undefined,
prices: [],
}
},
}
const priceSelectionStrategy = {
withTransaction: (manager) => {
return priceSelectionStrat
},
}
const productVariantRepository = MockRepository({ const productVariantRepository = MockRepository({
findOne: (query) => Promise.resolve({ id: IdMap.getId("ironman") }), findOne: (query) => Promise.resolve({ id: IdMap.getId("ironman") }),
}) })
@@ -246,6 +293,8 @@ describe("ProductVariantService", () => {
moneyAmountRepository, moneyAmountRepository,
productVariantRepository, productVariantRepository,
productOptionValueRepository, productOptionValueRepository,
cartRepository,
priceSelectionStrategy,
}) })
productVariantService.updateOptionValue = jest productVariantService.updateOptionValue = jest
@@ -464,13 +513,13 @@ describe("ProductVariantService", () => {
describe("getRegionPrice", () => { describe("getRegionPrice", () => {
const regionService = { const regionService = {
retrieve: function() { retrieve: function () {
return Promise.resolve({ return Promise.resolve({
id: IdMap.getId("california"), id: IdMap.getId("california"),
name: "California", name: "California",
}) })
}, },
withTransaction: function() { withTransaction: function () {
return this return this
}, },
} }
@@ -498,11 +547,35 @@ describe("ProductVariantService", () => {
}, },
}) })
const cartRepository = MockRepository({
findOne: (data) => {
return Promise.resolve({})
},
})
const priceSelectionStrat = {
calculateVariantPrice: (variantId, context) => {
return Promise.resolve({
originalPrice: null,
calculatedPrice: 1000,
prices: [],
})
},
}
const priceSelectionStrategy = {
withTransaction: (manager) => {
return priceSelectionStrat
},
}
const productVariantService = new ProductVariantService({ const productVariantService = new ProductVariantService({
manager: MockManager, manager: MockManager,
eventBusService, eventBusService,
regionService, regionService,
moneyAmountRepository, moneyAmountRepository,
cartRepository,
priceSelectionStrategy,
}) })
beforeEach(async () => { beforeEach(async () => {
@@ -3,7 +3,7 @@ import ProductService from "../product"
const eventBusService = { const eventBusService = {
emit: jest.fn(), emit: jest.fn(),
withTransaction: function() { withTransaction: function () {
return this return this
}, },
} }
@@ -54,7 +54,7 @@ describe("ProductService", () => {
describe("create", () => { describe("create", () => {
const productRepository = MockRepository({ const productRepository = MockRepository({
create: product => ({ create: (product) => ({
id: IdMap.getId("ironman"), id: IdMap.getId("ironman"),
title: "Suit", title: "Suit",
options: [], options: [],
@@ -71,7 +71,7 @@ describe("ProductService", () => {
const productTagRepository = MockRepository({ const productTagRepository = MockRepository({
findOne: () => Promise.resolve(undefined), findOne: () => Promise.resolve(undefined),
create: data => { create: (data) => {
if (data.value === "title") { if (data.value === "title") {
return { id: "tag-1", value: "title" } return { id: "tag-1", value: "title" }
} }
@@ -83,16 +83,16 @@ describe("ProductService", () => {
}) })
const productTypeRepository = MockRepository({ const productTypeRepository = MockRepository({
findOne: () => Promise.resolve(undefined), findOne: () => Promise.resolve(undefined),
create: data => { create: (data) => {
return { id: "type", value: "type1" } return { id: "type", value: "type1" }
}, },
}) })
const productCollectionService = { const productCollectionService = {
withTransaction: function() { withTransaction: function () {
return this return this
}, },
retrieve: id => retrieve: (id) =>
Promise.resolve({ id: IdMap.getId("cat"), title: "Suits" }), Promise.resolve({ id: IdMap.getId("cat"), title: "Suits" }),
} }
@@ -223,7 +223,7 @@ describe("ProductService", () => {
const productTypeRepository = MockRepository({ const productTypeRepository = MockRepository({
findOne: () => Promise.resolve(undefined), findOne: () => Promise.resolve(undefined),
create: data => { create: (data) => {
return { id: "type", value: "type1" } return { id: "type", value: "type1" }
}, },
}) })
@@ -231,7 +231,7 @@ describe("ProductService", () => {
const productVariantRepository = MockRepository() const productVariantRepository = MockRepository()
const productVariantService = { const productVariantService = {
withTransaction: function() { withTransaction: function () {
return this return this
}, },
update: (variant, update) => { update: (variant, update) => {
@@ -243,7 +243,7 @@ describe("ProductService", () => {
} }
const productTagRepository = MockRepository({ const productTagRepository = MockRepository({
findOne: data => { findOne: (data) => {
if (data.where.value === "test") { if (data.where.value === "test") {
return Promise.resolve({ id: IdMap.getId("test"), value: "test" }) return Promise.resolve({ id: IdMap.getId("test"), value: "test" })
} }
@@ -253,6 +253,25 @@ describe("ProductService", () => {
}, },
}) })
const cartRepository = MockRepository({
findOne: (data) => {
return Promise.resolve({})
},
})
const priceSelectionStrategy = {
withTransaction: (manager) => {
return this
},
calculateVariantPrice: (variantId, context) => {
return {
originalPrice: null,
calculatedPrice: null,
prices: [],
}
},
}
const productService = new ProductService({ const productService = new ProductService({
manager: MockManager, manager: MockManager,
productRepository, productRepository,
@@ -261,6 +280,8 @@ describe("ProductService", () => {
productTagRepository, productTagRepository,
productTypeRepository, productTypeRepository,
eventBusService, eventBusService,
cartRepository,
priceSelectionStrategy,
}) })
beforeEach(() => { beforeEach(() => {
@@ -440,7 +461,7 @@ describe("ProductService", () => {
describe("addOption", () => { describe("addOption", () => {
const productRepository = MockRepository({ const productRepository = MockRepository({
findOneWithRelations: query => findOneWithRelations: (query) =>
Promise.resolve({ Promise.resolve({
id: IdMap.getId("ironman"), id: IdMap.getId("ironman"),
options: [{ title: "Color" }], options: [{ title: "Color" }],
@@ -449,7 +470,7 @@ describe("ProductService", () => {
}) })
const productVariantService = { const productVariantService = {
withTransaction: function() { withTransaction: function () {
return this return this
}, },
addOptionValue: jest.fn(), addOptionValue: jest.fn(),
@@ -513,7 +534,7 @@ describe("ProductService", () => {
describe("reorderVariants", () => { describe("reorderVariants", () => {
const productRepository = MockRepository({ const productRepository = MockRepository({
findOneWithRelations: query => findOneWithRelations: (query) =>
Promise.resolve({ Promise.resolve({
id: IdMap.getId("ironman"), id: IdMap.getId("ironman"),
variants: [{ id: IdMap.getId("green") }, { id: IdMap.getId("blue") }], variants: [{ id: IdMap.getId("green") }, { id: IdMap.getId("blue") }],
@@ -571,7 +592,7 @@ describe("ProductService", () => {
describe("reorderOptions", () => { describe("reorderOptions", () => {
const productRepository = MockRepository({ const productRepository = MockRepository({
findOneWithRelations: query => findOneWithRelations: (query) =>
Promise.resolve({ Promise.resolve({
id: IdMap.getId("ironman"), id: IdMap.getId("ironman"),
options: [ options: [
@@ -637,7 +658,7 @@ describe("ProductService", () => {
describe("updateOption", () => { describe("updateOption", () => {
const productRepository = MockRepository({ const productRepository = MockRepository({
findOneWithRelations: query => findOneWithRelations: (query) =>
Promise.resolve({ Promise.resolve({
id: IdMap.getId("ironman"), id: IdMap.getId("ironman"),
options: [ options: [
@@ -712,7 +733,7 @@ describe("ProductService", () => {
describe("deleteOption", () => { describe("deleteOption", () => {
const productRepository = MockRepository({ const productRepository = MockRepository({
findOneWithRelations: query => findOneWithRelations: (query) =>
Promise.resolve({ Promise.resolve({
id: IdMap.getId("ironman"), id: IdMap.getId("ironman"),
variants: [ variants: [
@@ -751,7 +772,7 @@ describe("ProductService", () => {
}) })
const productOptionRepository = MockRepository({ const productOptionRepository = MockRepository({
findOne: query => { findOne: (query) => {
if (query.where.id === IdMap.getId("material")) { if (query.where.id === IdMap.getId("material")) {
return undefined return undefined
} }
+79 -36
View File
@@ -1,5 +1,9 @@
import _ from "lodash" import _ from "lodash"
import { EntityManager, DeepPartial } from "typeorm" import {
EntityManager,
DeepPartial,
AlreadyHasActiveConnectionError,
} from "typeorm"
import { MedusaError, Validator } from "medusa-core-utils" import { MedusaError, Validator } from "medusa-core-utils"
import { BaseService } from "medusa-interfaces" import { BaseService } from "medusa-interfaces"
@@ -38,6 +42,7 @@ import GiftCardService from "./gift-card"
import TotalsService from "./totals" import TotalsService from "./totals"
import InventoryService from "./inventory" import InventoryService from "./inventory"
import CustomShippingOptionService from "./custom-shipping-option" import CustomShippingOptionService from "./custom-shipping-option"
import { IPriceSelectionStrategy } from "../interfaces/price-selection-strategy"
type CartConstructorProps = { type CartConstructorProps = {
manager: EntityManager manager: EntityManager
@@ -59,6 +64,7 @@ type CartConstructorProps = {
totalsService: TotalsService totalsService: TotalsService
inventoryService: InventoryService inventoryService: InventoryService
customShippingOptionService: CustomShippingOptionService customShippingOptionService: CustomShippingOptionService
priceSelectionStrategy: IPriceSelectionStrategy
} }
type TotalsConfig = { type TotalsConfig = {
@@ -94,6 +100,7 @@ class CartService extends BaseService {
private paymentSessionRepository_: typeof PaymentSessionRepository private paymentSessionRepository_: typeof PaymentSessionRepository
private inventoryService_: InventoryService private inventoryService_: InventoryService
private customShippingOptionService_: CustomShippingOptionService private customShippingOptionService_: CustomShippingOptionService
private priceSelectionStrategy_: IPriceSelectionStrategy
constructor({ constructor({
manager, manager,
@@ -115,6 +122,7 @@ class CartService extends BaseService {
paymentSessionRepository, paymentSessionRepository,
inventoryService, inventoryService,
customShippingOptionService, customShippingOptionService,
priceSelectionStrategy,
}: CartConstructorProps) { }: CartConstructorProps) {
super() super()
@@ -137,6 +145,7 @@ class CartService extends BaseService {
this.inventoryService_ = inventoryService this.inventoryService_ = inventoryService
this.customShippingOptionService_ = customShippingOptionService this.customShippingOptionService_ = customShippingOptionService
this.taxProviderService_ = taxProviderService this.taxProviderService_ = taxProviderService
this.priceSelectionStrategy_ = priceSelectionStrategy
} }
withTransaction(transactionManager: EntityManager): CartService { withTransaction(transactionManager: EntityManager): CartService {
@@ -164,6 +173,7 @@ class CartService extends BaseService {
giftCardService: this.giftCardService_, giftCardService: this.giftCardService_,
inventoryService: this.inventoryService_, inventoryService: this.inventoryService_,
customShippingOptionService: this.customShippingOptionService_, customShippingOptionService: this.customShippingOptionService_,
priceSelectionStrategy: this.priceSelectionStrategy_,
}) })
cloned.transactionManager_ = transactionManager cloned.transactionManager_ = transactionManager
@@ -701,12 +711,6 @@ class CartService extends BaseService {
], ],
}) })
if (typeof update.region_id !== "undefined") {
const countryCode =
(update.country_code || update.shipping_address?.country_code) ?? null
await this.setRegion_(cart, update.region_id, countryCode)
}
if (typeof update.customer_id !== "undefined") { if (typeof update.customer_id !== "undefined") {
await this.updateCustomerId_(cart, update.customer_id) await this.updateCustomerId_(cart, update.customer_id)
} else { } else {
@@ -718,6 +722,19 @@ class CartService extends BaseService {
} }
} }
if (typeof update.region_id !== "undefined") {
const countryCode =
(update.country_code || update.shipping_address?.country_code) ?? null
await this.setRegion_(cart, update.region_id, countryCode)
}
if (
typeof update.customer_id !== "undefined" ||
typeof update.region_id !== "undefined"
) {
await this.updateUnitPrices(cart, update.region_id, update.customer_id)
}
const addrRepo = manager.getCustomRepository(this.addressRepository_) const addrRepo = manager.getCustomRepository(this.addressRepository_)
if ("shipping_address_id" in update || "shipping_address" in update) { if ("shipping_address_id" in update || "shipping_address" in update) {
let address: string | Partial<Address> | undefined let address: string | Partial<Address> | undefined
@@ -1169,7 +1186,10 @@ class CartService extends BaseService {
* this could be IP address or similar for fraud handling. * this could be IP address or similar for fraud handling.
* @return the resulting cart * @return the resulting cart
*/ */
async authorizePayment(cartId: string, context: Record<string, any> = {}): Promise<Cart> { async authorizePayment(
cartId: string,
context: Record<string, any> = {}
): Promise<Cart> {
return this.atomicPhase_(async (manager: EntityManager) => { return this.atomicPhase_(async (manager: EntityManager) => {
const cartRepository = manager.getCustomRepository(this.cartRepository_) const cartRepository = manager.getCustomRepository(this.cartRepository_)
@@ -1592,6 +1612,57 @@ class CartService extends BaseService {
return customOption return customOption
} }
async updateUnitPrices(
cart: Cart,
regionId?: string,
customer_id?: string
): Promise<void> {
// If the cart contains items, we update the price of the items
// to match the updated region or customer id (keeping the old
// value if it exists)
if (cart.items.length) {
const region = await this.regionService_.retrieve(
regionId || cart.region_id,
{
relations: ["countries"],
}
)
cart.items = await Promise.all(
cart.items
.map(async (item) => {
const availablePrice = await this.priceSelectionStrategy_
.calculateVariantPrice(item.variant_id, {
region_id: region.id,
currency_code: region.currency_code,
quantity: item.quantity,
customer_id: customer_id || cart.customer_id,
include_discount_prices: true,
})
.catch(() => undefined)
if (
availablePrice !== undefined &&
availablePrice.calculatedPrice !== null
) {
return this.lineItemService_
.withTransaction(this.transactionManager_)
.update(item.id, {
has_shipping: false,
unit_price: availablePrice.calculatedPrice,
})
} else {
await this.lineItemService_
.withTransaction(this.transactionManager_)
.delete(item.id)
return null
}
})
.filter(Boolean)
)
}
}
/** /**
* Set's the region of a cart. * Set's the region of a cart.
* @param cart - the cart to set region on * @param cart - the cart to set region on
@@ -1619,34 +1690,6 @@ class CartService extends BaseService {
cart.region = region cart.region = region
cart.region_id = region.id cart.region_id = region.id
// If the cart contains items we want to change the unit_price field of each
// item to correspond to the price given in the region
if (cart.items.length) {
cart.items = await Promise.all(
cart.items
.map(async (item) => {
const availablePrice = await this.productVariantService_
.getRegionPrice(item.variant_id, regionId)
.catch(() => undefined)
if (availablePrice !== undefined) {
return this.lineItemService_
.withTransaction(this.transactionManager_)
.update(item.id, {
has_shipping: false,
unit_price: availablePrice,
})
} else {
await this.lineItemService_
.withTransaction(this.transactionManager_)
.delete(item.id)
return null
}
})
.filter(Boolean)
)
}
/* /*
* When changing the region you are changing the set of countries that your * When changing the region you are changing the set of countries that your
* cart can be shipped to so we need to make sure that the current shipping * cart can be shipped to so we need to make sure that the current shipping
+7 -1
View File
@@ -143,6 +143,7 @@ class LineItemService extends BaseService {
.withTransaction(manager) .withTransaction(manager)
.retrieve(variantId, { .retrieve(variantId, {
relations: ["product"], relations: ["product"],
include_discount_prices: true,
}) })
const region = await this.regionService_ const region = await this.regionService_
@@ -164,7 +165,12 @@ class LineItemService extends BaseService {
} else { } else {
price = await this.productVariantService_ price = await this.productVariantService_
.withTransaction(manager) .withTransaction(manager)
.getRegionPrice(variant.id, region.id) .getRegionPrice(variant.id, {
regionId: region.id,
quantity: quantity,
customer_id: undefined,
include_discount_prices: true,
})
} }
const toCreate = { const toCreate = {
+220 -40
View File
@@ -1,10 +1,16 @@
import { JsonWebTokenError } from "jsonwebtoken"
import { MedusaError } from "medusa-core-utils" import { MedusaError } from "medusa-core-utils"
import { BaseService } from "medusa-interfaces" import { BaseService } from "medusa-interfaces"
import { Brackets, EntityManager, ILike, SelectQueryBuilder } from "typeorm" import { Brackets, EntityManager, ILike, SelectQueryBuilder } from "typeorm"
import { MoneyAmount } from ".." import {
IPriceSelectionStrategy,
PriceSelectionContext,
} from "../interfaces/price-selection-strategy"
import { MoneyAmount } from "../models/money-amount"
import { Product } from "../models/product" import { Product } from "../models/product"
import { ProductOptionValue } from "../models/product-option-value" import { ProductOptionValue } from "../models/product-option-value"
import { ProductVariant } from "../models/product-variant" import { ProductVariant } from "../models/product-variant"
import { CartRepository } from "../repositories/cart"
import { MoneyAmountRepository } from "../repositories/money-amount" import { MoneyAmountRepository } from "../repositories/money-amount"
import { ProductRepository } from "../repositories/product" import { ProductRepository } from "../repositories/product"
import { ProductOptionValueRepository } from "../repositories/product-option-value" import { ProductOptionValueRepository } from "../repositories/product-option-value"
@@ -18,6 +24,7 @@ import { FindConfig } from "../types/common"
import { import {
CreateProductVariantInput, CreateProductVariantInput,
FilterableProductVariantProps, FilterableProductVariantProps,
GetRegionPriceContext,
ProductVariantPrice, ProductVariantPrice,
UpdateProductVariantInput, UpdateProductVariantInput,
} from "../types/product-variant" } from "../types/product-variant"
@@ -38,8 +45,10 @@ class ProductVariantService extends BaseService {
private productRepository_: typeof ProductRepository private productRepository_: typeof ProductRepository
private eventBus_: EventBusService private eventBus_: EventBusService
private regionService_: RegionService private regionService_: RegionService
private priceSelectionStrategy_: IPriceSelectionStrategy
private moneyAmountRepository_: typeof MoneyAmountRepository private moneyAmountRepository_: typeof MoneyAmountRepository
private productOptionValueRepository_: typeof ProductOptionValueRepository private productOptionValueRepository_: typeof ProductOptionValueRepository
private cartRepository_: typeof CartRepository
constructor({ constructor({
manager, manager,
@@ -49,6 +58,8 @@ class ProductVariantService extends BaseService {
regionService, regionService,
moneyAmountRepository, moneyAmountRepository,
productOptionValueRepository, productOptionValueRepository,
cartRepository,
priceSelectionStrategy,
}) { }) {
super() super()
@@ -70,6 +81,10 @@ class ProductVariantService extends BaseService {
this.moneyAmountRepository_ = moneyAmountRepository this.moneyAmountRepository_ = moneyAmountRepository
this.productOptionValueRepository_ = productOptionValueRepository this.productOptionValueRepository_ = productOptionValueRepository
this.cartRepository_ = cartRepository
this.priceSelectionStrategy_ = priceSelectionStrategy
} }
withTransaction(transactionManager: EntityManager): ProductVariantService { withTransaction(transactionManager: EntityManager): ProductVariantService {
@@ -85,6 +100,8 @@ class ProductVariantService extends BaseService {
regionService: this.regionService_, regionService: this.regionService_,
moneyAmountRepository: this.moneyAmountRepository_, moneyAmountRepository: this.moneyAmountRepository_,
productOptionValueRepository: this.productOptionValueRepository_, productOptionValueRepository: this.productOptionValueRepository_,
cartRepository: this.cartRepository_,
priceSelectionStrategy: this.priceSelectionStrategy_,
}) })
cloned.transactionManager_ = transactionManager cloned.transactionManager_ = transactionManager
@@ -100,12 +117,21 @@ class ProductVariantService extends BaseService {
*/ */
async retrieve( async retrieve(
variantId: string, variantId: string,
config: FindConfig<ProductVariant> = {} config: FindConfig<ProductVariant> & PriceSelectionContext = {
include_discount_prices: false,
}
): Promise<ProductVariant> { ): Promise<ProductVariant> {
const variantRepo = this.manager_.getCustomRepository( const variantRepo = this.manager_.getCustomRepository(
this.productVariantRepository_ this.productVariantRepository_
) )
const validatedId = this.validateId_(variantId) const validatedId = this.validateId_(variantId)
const priceIndex = config.relations?.indexOf("prices") ?? -1
if (priceIndex >= 0 && config.relations) {
config.relations = [...config.relations]
config.relations.splice(priceIndex, 1)
}
const query = this.buildQuery_({ id: validatedId }, config) const query = this.buildQuery_({ id: validatedId }, config)
const variant = await variantRepo.findOne(query) const variant = await variantRepo.findOne(query)
@@ -116,7 +142,16 @@ class ProductVariantService extends BaseService {
) )
} }
return variant return priceIndex >= 0
? ((await this.setAdditionalPrices(
variant,
config.currency_code,
config.region_id,
config.cart_id,
config.customer_id,
config.include_discount_prices
)) as ProductVariant)
: variant
} }
/** /**
@@ -127,11 +162,20 @@ class ProductVariantService extends BaseService {
*/ */
async retrieveBySKU( async retrieveBySKU(
sku: string, sku: string,
config: FindConfig<ProductVariant> = {} config: FindConfig<ProductVariant> & PriceSelectionContext = {
include_discount_prices: false,
}
): Promise<ProductVariant> { ): Promise<ProductVariant> {
const variantRepo = this.manager_.getCustomRepository( const variantRepo = this.manager_.getCustomRepository(
this.productVariantRepository_ this.productVariantRepository_
) )
const priceIndex = config.relations?.indexOf("prices") ?? -1
if (priceIndex >= 0 && config.relations) {
config.relations = [...config.relations]
config.relations.splice(priceIndex, 1)
}
const query = this.buildQuery_({ sku }, config) const query = this.buildQuery_({ sku }, config)
const variant = await variantRepo.findOne(query) const variant = await variantRepo.findOne(query)
@@ -142,7 +186,16 @@ class ProductVariantService extends BaseService {
) )
} }
return variant return priceIndex >= 0
? ((await this.setAdditionalPrices(
variant,
config.currency_code,
config.region_id,
config.cart_id,
config.customer_id,
config.include_discount_prices
)) as ProductVariant)
: variant
} }
/** /**
@@ -254,6 +307,7 @@ class ProductVariantService extends BaseService {
* The function will throw, if price updates are attempted. * The function will throw, if price updates are attempted.
* @param {string | ProductVariant} variantOrVariantId - variant or id of a variant. * @param {string | ProductVariant} variantOrVariantId - variant or id of a variant.
* @param {object} update - an object with the update values. * @param {object} update - an object with the update values.
* @param {object} config - an object with the config values for returning the variant.
* @return {Promise} resolves to the update result. * @return {Promise} resolves to the update result.
*/ */
async update( async update(
@@ -267,7 +321,17 @@ class ProductVariantService extends BaseService {
let variant = variantOrVariantId as ProductVariant let variant = variantOrVariantId as ProductVariant
if (typeof variant === `string`) { if (typeof variant === `string`) {
variant = await this.retrieve(variantOrVariantId as string) const variantRes = await variantRepo.findOne({
where: { id: variantOrVariantId as string },
})
if (typeof variant === "undefined") {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Variant with id ${variantOrVariantId} was not found`
)
} else {
variant = variantRes as ProductVariant
}
} else if (!variant.id) { } else if (!variant.id) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.INVALID_DATA, MedusaError.Types.INVALID_DATA,
@@ -291,12 +355,12 @@ class ProductVariantService extends BaseService {
} }
} }
if (metadata) { if (typeof metadata === "object") {
variant.metadata = this.setMetadata_(variant, metadata) variant.metadata = this.setMetadata_(variant, metadata as object)
} }
if (typeof inventory_quantity === "number") { if (typeof inventory_quantity === "number") {
variant.inventory_quantity = inventory_quantity variant.inventory_quantity = inventory_quantity as number
} }
for (const [key, value] of Object.entries(rest)) { for (const [key, value] of Object.entries(rest)) {
@@ -312,6 +376,7 @@ class ProductVariantService extends BaseService {
product_id: result.product_id, product_id: result.product_id,
fields: Object.keys(update), fields: Object.keys(update),
}) })
return result return result
}) })
} }
@@ -358,41 +423,29 @@ class ProductVariantService extends BaseService {
* exists the function will try to use a currency price. If no default * exists the function will try to use a currency price. If no default
* currency price exists the function will throw an error. * currency price exists the function will throw an error.
* @param {string} variantId - the id of the variant to get price from * @param {string} variantId - the id of the variant to get price from
* @param {string} regionId - the id of the region to get price for * @param {GetRegionPriceContext} context - context for getting region price
* @return {number} the price specific to the region * @return {number} the price specific to the region
*/ */
async getRegionPrice(variantId: string, regionId: string): Promise<number> { async getRegionPrice(
variantId: string,
context: GetRegionPriceContext
): Promise<number> {
return this.atomicPhase_(async (manager: EntityManager) => { return this.atomicPhase_(async (manager: EntityManager) => {
const moneyAmountRepo = manager.getCustomRepository(
this.moneyAmountRepository_
)
const region = await this.regionService_ const region = await this.regionService_
.withTransaction(manager) .withTransaction(manager)
.retrieve(regionId) .retrieve(context.regionId)
// Find region price based on region id const prices = await this.priceSelectionStrategy_
let moneyAmount = await moneyAmountRepo.findOne({ .withTransaction(manager)
where: { region_id: regionId, variant_id: variantId }, .calculateVariantPrice(variantId, {
}) region_id: context.regionId,
currency_code: region.currency_code,
// If no price could be find based on region id, we try to fetch quantity: context.quantity,
// based on the region currency code customer_id: context.customer_id,
if (!moneyAmount) { include_discount_prices: !!context.include_discount_prices,
moneyAmount = await moneyAmountRepo.findOne({
where: { variant_id: variantId, currency_code: region.currency_code },
}) })
}
// Still, if no price is found, we throw return prices.calculatedPrice
if (!moneyAmount) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`A price for region: ${region.name} could not be found`
)
}
return moneyAmount.amount
}) })
} }
@@ -554,12 +607,23 @@ class ProductVariantService extends BaseService {
*/ */
async listAndCount( async listAndCount(
selector: FilterableProductVariantProps, selector: FilterableProductVariantProps,
config: FindConfig<ProductVariant> = { relations: [], skip: 0, take: 20 } config: FindConfig<ProductVariant> & PriceSelectionContext = {
relations: [],
skip: 0,
take: 20,
include_discount_prices: false,
}
): Promise<[ProductVariant[], number]> { ): Promise<[ProductVariant[], number]> {
const variantRepo = this.manager_.getCustomRepository( const variantRepo = this.manager_.getCustomRepository(
this.productVariantRepository_ this.productVariantRepository_
) )
const priceIndex = config.relations?.indexOf("prices") ?? -1
if (priceIndex >= 0 && config.relations) {
config.relations = [...config.relations]
config.relations.splice(priceIndex, 1)
}
const { q, query, relations } = this.prepareListQuery_(selector, config) const { q, query, relations } = this.prepareListQuery_(selector, config)
if (q) { if (q) {
@@ -571,10 +635,39 @@ class ProductVariantService extends BaseService {
raw.map((i) => i.id), raw.map((i) => i.id),
query.withDeleted ?? false query.withDeleted ?? false
) )
if (priceIndex >= 0) {
const res = await this.setAdditionalPrices(
variants,
config.currency_code,
config.region_id,
config.cart_id,
config.customer_id,
config.include_discount_prices
)
return [res as ProductVariant[], count]
}
return [variants, count] return [variants, count]
} }
return await variantRepo.findWithRelationsAndCount(relations, query) const [variants, count] = await variantRepo.findWithRelationsAndCount(
relations,
query
)
if (priceIndex >= 0) {
const res = await this.setAdditionalPrices(
variants,
config.currency_code,
config.region_id,
config.cart_id,
config.customer_id,
config.include_discount_prices
)
return [res as ProductVariant[], count]
}
return [variants, count]
} }
/** /**
@@ -584,12 +677,22 @@ class ProductVariantService extends BaseService {
*/ */
async list( async list(
selector: FilterableProductVariantProps, selector: FilterableProductVariantProps,
config: FindConfig<ProductVariant> = { relations: [], skip: 0, take: 20 } config: FindConfig<ProductVariant> & PriceSelectionContext = {
relations: [],
skip: 0,
take: 20,
}
): Promise<ProductVariant[]> { ): Promise<ProductVariant[]> {
const productVariantRepo = this.manager_.getCustomRepository( const productVariantRepo = this.manager_.getCustomRepository(
this.productVariantRepository_ this.productVariantRepository_
) )
const priceIndex = config.relations?.indexOf("prices") ?? -1
if (priceIndex >= 0 && config.relations) {
config.relations = [...config.relations]
config.relations.splice(priceIndex, 1)
}
let q: string | undefined let q: string | undefined
if ("q" in selector) { if ("q" in selector) {
q = selector.q q = selector.q
@@ -620,7 +723,18 @@ class ProductVariantService extends BaseService {
} }
} }
return await productVariantRepo.find(query) const variants = await productVariantRepo.find(query)
return priceIndex >= 0
? ((await this.setAdditionalPrices(
variants,
config.currency_code,
config.region_id,
config.cart_id,
config.customer_id,
config.include_discount_prices
)) as ProductVariant[])
: variants
} }
/** /**
@@ -770,6 +884,72 @@ class ProductVariantService extends BaseService {
return qb return qb
} }
/**
* Set additional prices on a list of variants.
* @param {ProductVariant | ProductVariant[] } variant variant on which to set additional prices
* @param {string} currency_code currency code to fetch prices for
* @param {string} region_id region to fetch prices for
* @param {string} cart_id string of cart to use as a basis for getting currency and region
* @param {string} customer_id id of potentially logged in customer, used to get prices valid for their customer groups
* @param {boolean} include_discount_prices should result include discount pricing
* @return {Promise<ProductVariant | ProductVariant[]>} A list of variants with variants decorated with "additional_prices"
*/
async setAdditionalPrices(
variant,
currency_code,
region_id,
cart_id,
customer_id,
include_discount_prices = false
): Promise<ProductVariant | ProductVariant[]> {
return this.atomicPhase_(async (manager) => {
const cartRepo = manager.getCustomRepository(this.cartRepository_)
let regionId = region_id
let currencyCode = currency_code
if (cart_id) {
const cart = await cartRepo.findOne({
where: { id: cart_id },
relations: ["region"],
})
regionId = cart.region.id
currencyCode = cart.region.currency_code
}
const variantArray = Array.isArray(variant) ? variant : [variant]
const priceSelectionStrategy =
this.priceSelectionStrategy_.withTransaction(manager)
const variantsWithPrices = await Promise.all(
variantArray.map(async (v) => {
const prices = await priceSelectionStrategy.calculateVariantPrice(
v.id,
{
region_id: regionId,
currency_code: currencyCode,
cart_id: cart_id,
customer_id: customer_id,
include_discount_prices: include_discount_prices,
}
)
return {
...v,
prices: prices.prices,
original_price: prices.originalPrice,
calculated_price: prices.calculatedPrice,
calculated_price_type: prices.calculatedPriceType,
}
})
)
return Array.isArray(variant) ? variantsWithPrices : variantsWithPrices[0]
})
}
} }
export default ProductVariantService export default ProductVariantService
+209 -11
View File
@@ -27,6 +27,8 @@ class ProductService extends BaseService {
productTagRepository, productTagRepository,
imageRepository, imageRepository,
searchService, searchService,
cartRepository,
priceSelectionStrategy,
}) { }) {
super() super()
@@ -62,6 +64,12 @@ class ProductService extends BaseService {
/** @private @const {SearchService} */ /** @private @const {SearchService} */
this.searchService_ = searchService this.searchService_ = searchService
/** @private @const {CartRepository} */
this.cartRepository_ = cartRepository
/** @private @const {IPriceSelectionStrategy} */
this.priceSelectionStrategy_ = priceSelectionStrategy
} }
withTransaction(transactionManager) { withTransaction(transactionManager) {
@@ -80,6 +88,8 @@ class ProductService extends BaseService {
productTagRepository: this.productTagRepository_, productTagRepository: this.productTagRepository_,
productTypeRepository: this.productTypeRepository_, productTypeRepository: this.productTypeRepository_,
imageRepository: this.imageRepository_, imageRepository: this.imageRepository_,
cartRepository: this.cartRepository_,
priceSelectionStrategy: this.priceSelectionStrategy_,
}) })
cloned.transactionManager_ = transactionManager cloned.transactionManager_ = transactionManager
@@ -95,10 +105,23 @@ class ProductService extends BaseService {
* returned * returned
* @return {Promise<Product[]>} the result of the find operation * @return {Promise<Product[]>} the result of the find operation
*/ */
async list(selector = {}, config = { relations: [], skip: 0, take: 20 }) { async list(
selector = {},
config = {
relations: [],
skip: 0,
take: 20,
include_discount_prices: false,
}
) {
const productRepo = this.manager_.getCustomRepository( const productRepo = this.manager_.getCustomRepository(
this.productRepository_ this.productRepository_
) )
const priceIndex = config.relations?.indexOf("variants.prices") ?? -1
if (priceIndex >= 0 && config.relations) {
config.relations = [...config.relations]
config.relations.splice(priceIndex, 1)
}
const { q, query, relations } = this.prepareListQuery_(selector, config) const { q, query, relations } = this.prepareListQuery_(selector, config)
@@ -112,7 +135,18 @@ class ProductService extends BaseService {
) )
} }
return productRepo.findWithRelations(relations, query) const products = productRepo.findWithRelations(relations, query)
return priceIndex > -1
? await this.setAdditionalPrices(
products,
config.currency_code,
config.region_id,
config.cart_id,
config.customer_id,
config.include_discount_prices
)
: products
} }
/** /**
@@ -128,12 +162,23 @@ class ProductService extends BaseService {
*/ */
async listAndCount( async listAndCount(
selector = {}, selector = {},
config = { relations: [], skip: 0, take: 20 } config = {
relations: [],
skip: 0,
take: 20,
include_discount_prices: false,
}
) { ) {
const productRepo = this.manager_.getCustomRepository( const productRepo = this.manager_.getCustomRepository(
this.productRepository_ this.productRepository_
) )
const priceIndex = config.relations?.indexOf("variants.prices") ?? -1
if (priceIndex >= 0 && config.relations) {
config.relations = [...config.relations]
config.relations.splice(priceIndex, 1)
}
const { q, query, relations } = this.prepareListQuery_(selector, config) const { q, query, relations } = this.prepareListQuery_(selector, config)
if (q) { if (q) {
@@ -148,7 +193,25 @@ class ProductService extends BaseService {
return [products, count] return [products, count]
} }
return await productRepo.findWithRelationsAndCount(relations, query) const [products, count] = await productRepo.findWithRelationsAndCount(
relations,
query
)
if (priceIndex > -1) {
const productsWithAdditionalPrices = await this.setAdditionalPrices(
products,
config.currency_code,
config.region_id,
config.cart_id,
config.customer_id,
config.include_discount_prices
)
return [productsWithAdditionalPrices, count]
} else {
return [products, count]
}
} }
/** /**
@@ -172,12 +235,18 @@ class ProductService extends BaseService {
* query response * query response
* @return {Promise<Product>} the result of the find one operation. * @return {Promise<Product>} the result of the find one operation.
*/ */
async retrieve(productId, config = {}) { async retrieve(productId, config = { include_discount_prices: false }) {
const productRepo = this.manager_.getCustomRepository( const productRepo = this.manager_.getCustomRepository(
this.productRepository_ this.productRepository_
) )
const validatedId = this.validateId_(productId) const validatedId = this.validateId_(productId)
const priceIndex = config.relations?.indexOf("variants.prices") ?? -1
if (priceIndex >= 0 && config.relations) {
config.relations = [...config.relations]
config.relations.splice(priceIndex, 1)
}
const query = { where: { id: validatedId } } const query = { where: { id: validatedId } }
if (config.relations && config.relations.length > 0) { if (config.relations && config.relations.length > 0) {
@@ -199,7 +268,16 @@ class ProductService extends BaseService {
) )
} }
return product return priceIndex > -1
? await this.setAdditionalPrices(
product,
config.currency_code,
config.region_id,
config.cart_id,
config.customer_id,
config.include_discount_prices
)
: product
} }
/** /**
@@ -214,6 +292,12 @@ class ProductService extends BaseService {
this.productRepository_ this.productRepository_
) )
const priceIndex = config.relations?.indexOf("variants.prices") ?? -1
if (priceIndex >= 0 && config.relations) {
config.relations = [...config.relations]
config.relations.splice(priceIndex, 1)
}
const query = { where: { handle: productHandle } } const query = { where: { handle: productHandle } }
if (config.relations && config.relations.length > 0) { if (config.relations && config.relations.length > 0) {
@@ -235,7 +319,16 @@ class ProductService extends BaseService {
) )
} }
return product return priceIndex > -1
? await this.setAdditionalPrices(
product,
config.currency_code,
config.region_id,
config.cart_id,
config.customer_id,
config.include_discount_prices
)
: product
} }
/** /**
@@ -250,6 +343,12 @@ class ProductService extends BaseService {
this.productRepository_ this.productRepository_
) )
const priceIndex = config.relations?.indexOf("variants.prices") ?? -1
if (priceIndex >= 0 && config.relations) {
config.relations = [...config.relations]
config.relations.splice(priceIndex, 1)
}
const query = { where: { external_id: externalId } } const query = { where: { external_id: externalId } }
if (config.relations && config.relations.length > 0) { if (config.relations && config.relations.length > 0) {
@@ -271,7 +370,16 @@ class ProductService extends BaseService {
) )
} }
return product return priceIndex > -1
? await this.setAdditionalPrices(
product,
config.currency_code,
config.region_id,
config.cart_id,
config.customer_id,
config.include_discount_prices
)
: product
} }
/** /**
@@ -535,6 +643,7 @@ class ProductService extends BaseService {
} }
const result = await productRepo.save(product) const result = await productRepo.save(product)
await this.eventBus_ await this.eventBus_
.withTransaction(manager) .withTransaction(manager)
.emit(ProductService.Events.UPDATED, { .emit(ProductService.Events.UPDATED, {
@@ -821,11 +930,18 @@ class ProductService extends BaseService {
* @param {string} productId - the productId to decorate. * @param {string} productId - the productId to decorate.
* @param {string[]} fields - the fields to include. * @param {string[]} fields - the fields to include.
* @param {string[]} expandFields - fields to expand. * @param {string[]} expandFields - fields to expand.
* @param {object} config - retrieve config for price calculation.
* @return {Product} return the decorated product. * @return {Product} return the decorated product.
*/ */
async decorate(productId, fields = [], expandFields = []) { async decorate(productId, fields = [], expandFields = [], config = {}) {
const requiredFields = ["id", "metadata"] const requiredFields = ["id", "metadata"]
const priceIndex = expandFields.indexOf("variants.prices") ?? -1
if (priceIndex >= 0 && expandFields.length) {
expandFields = [...expandFields]
expandFields.splice(priceIndex, 1)
}
fields = fields.concat(requiredFields) fields = fields.concat(requiredFields)
const product = await this.retrieve(productId, { const product = await this.retrieve(productId, {
@@ -833,8 +949,16 @@ class ProductService extends BaseService {
relations: expandFields, relations: expandFields,
}) })
// const final = await this.runDecorators_(decorated) return priceIndex > -1
return product ? await this.setAdditionalPrices(
product,
config.currency_code,
config.region_id,
config.cart_id,
config.customer_id,
config.include_discount_prices
)
: product
} }
/** /**
@@ -908,6 +1032,80 @@ class ProductService extends BaseService {
return qb return qb
} }
/**
* Set additional prices on a list of products.
* @param {Product[] | Product} products list of products on which to set additional prices
* @param {string} currency_code currency code to fetch prices for
* @param {string} region_id region to fetch prices for
* @param {string} cart_id string of cart to use as a basis for getting currency and region
* @param {string} customer_id id of potentially logged in customer, used to get prices valid for their customer groups
* @param {boolean} include_discount_prices indication wether or not to include sales prices in result
* @return {Promise<Product[]>} A list of products with variants decorated with "additional_prices"
*/
async setAdditionalPrices(
products,
currency_code,
region_id,
cart_id,
customer_id,
include_discount_prices = false
) {
return this.atomicPhase_(async (manager) => {
const cartRepo = this.manager_.getCustomRepository(this.cartRepository_)
let regionId = region_id
let currencyCode = currency_code
if (cart_id) {
const cart = await cartRepo.findOne({
where: { id: cart_id },
relations: ["region"],
})
regionId = cart.region.id
currencyCode = cart.region.currency_code
}
const productArray = Array.isArray(products) ? products : [products]
const priceSelectionStrategy =
this.priceSelectionStrategy_.withTransaction(manager)
const productsWithPrices = await Promise.all(
productArray.map(async (p) => {
if (p.variants?.length) {
p.variants = await Promise.all(
p.variants.map(async (v) => {
const prices =
await priceSelectionStrategy.calculateVariantPrice(v.id, {
region_id: regionId,
currency_code: currencyCode,
cart_id: cart_id,
customer_id: customer_id,
include_discount_prices,
})
return {
...v,
prices: prices.prices,
original_price: prices.originalPrice,
calculated_price: prices.calculatedPrice,
calculated_price_type: prices.calculatedPriceType,
}
})
)
}
return p
})
)
return Array.isArray(products)
? productsWithPrices
: productsWithPrices[0]
})
}
} }
export default ProductService export default ProductService
@@ -7,6 +7,7 @@ import {
} from "../interfaces/price-selection-strategy" } from "../interfaces/price-selection-strategy"
import { MoneyAmountRepository } from "../repositories/money-amount" import { MoneyAmountRepository } from "../repositories/money-amount"
import { EntityManager } from "typeorm" import { EntityManager } from "typeorm"
import { MedusaError } from "medusa-core-utils"
class PriceSelectionStrategy extends AbstractPriceSelectionStrategy { class PriceSelectionStrategy extends AbstractPriceSelectionStrategy {
private moneyAmountRepository_: typeof MoneyAmountRepository private moneyAmountRepository_: typeof MoneyAmountRepository
@@ -0,0 +1,15 @@
import { IsOptional, IsString } from "class-validator"
export class PriceSelectionParams {
@IsOptional()
@IsString()
cart_id?: string
@IsOptional()
@IsString()
region_id?: string
@IsOptional()
@IsString()
currency_code?: string
}
@@ -25,6 +25,13 @@ export type ProductVariantPrice = {
max_quantity?: number max_quantity?: number
} }
export type GetRegionPriceContext = {
regionId: string
quantity?: number
customer_id?: string
include_discount_prices?: boolean
}
export type ProductVariantOption = { export type ProductVariantOption = {
option_id: string option_id: string
value: string value: string