feat: Implement PriceList and extend MoneyAmount (#1152)
* init * added buld id validation to repo * admin done * updated price reqs * intial implementation of PriceList * integration tests for price lists * updated admin/product integration tests * update updateVariantPrices method * remove comment from error handler * add integration test for batch deleting prices associated with price list * make update to prices through variant service limited to default prices * update store/products.js snapshot * add api unit tests and update product integration tests to validate that prices from Price List are ignored * fix product test * requested changes * cascade * ensure delete variant cascades to MoneyAmount * addresses PR feedback * removed unused endpoint * update mock * fix failing store integration tests * remove medusajs ressource * re add env.template * Update integration-tests/api/__tests__/admin/price-list.js Co-authored-by: Philip Korsholm <88927411+pKorsholm@users.noreply.github.com> * Update integration-tests/api/__tests__/admin/price-list.js Co-authored-by: Philip Korsholm <88927411+pKorsholm@users.noreply.github.com> * fix: update snapshots Co-authored-by: Sebastian Rindom <skrindom@gmail.com> Co-authored-by: Philip Korsholm <88927411+pKorsholm@users.noreply.github.com>
This commit is contained in:
52 files changed
+4234
-505
No files matched your search
@@ -0,0 +1,293 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`/admin/price-lists GET /admin/price-lists returns a price list by :id 1`] = `
|
||||||
|
Object {
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"customer_groups": Array [],
|
||||||
|
"deleted_at": null,
|
||||||
|
"description": "Winter sale for VIP customers. 25% off selected items.",
|
||||||
|
"ends_at": "2022-07-31T00:00:00.000Z",
|
||||||
|
"id": Any<String>,
|
||||||
|
"name": "VIP winter sale",
|
||||||
|
"prices": Array [
|
||||||
|
Object {
|
||||||
|
"amount": 100,
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"currency_code": "usd",
|
||||||
|
"deleted_at": null,
|
||||||
|
"id": Any<String>,
|
||||||
|
"max_quantity": 100,
|
||||||
|
"min_quantity": 1,
|
||||||
|
"price_list_id": "pl_no_customer_groups",
|
||||||
|
"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": Any<String>,
|
||||||
|
"max_quantity": 500,
|
||||||
|
"min_quantity": 101,
|
||||||
|
"price_list_id": "pl_no_customer_groups",
|
||||||
|
"region_id": null,
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"variant_id": "test-variant",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"amount": 50,
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"currency_code": "usd",
|
||||||
|
"deleted_at": null,
|
||||||
|
"id": Any<String>,
|
||||||
|
"max_quantity": 1000,
|
||||||
|
"min_quantity": 501,
|
||||||
|
"price_list_id": "pl_no_customer_groups",
|
||||||
|
"region_id": null,
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"variant_id": "test-variant",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"starts_at": "2022-07-01T00:00:00.000Z",
|
||||||
|
"status": "active",
|
||||||
|
"type": "sale",
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`/admin/price-lists POST /admin/price-lists/:id updates a price list 1`] = `
|
||||||
|
Object {
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"customer_groups": Array [
|
||||||
|
Object {
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"deleted_at": null,
|
||||||
|
"id": "customer-group-1",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "vip-customers",
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"deleted_at": null,
|
||||||
|
"description": "Winter sale for our most loyal customers",
|
||||||
|
"ends_at": "2022-12-31T00:00:00.000Z",
|
||||||
|
"id": "pl_no_customer_groups",
|
||||||
|
"name": "Loyalty Reward - Winter Sale",
|
||||||
|
"prices": Array [
|
||||||
|
Object {
|
||||||
|
"amount": 100,
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"currency_code": "usd",
|
||||||
|
"deleted_at": null,
|
||||||
|
"id": Any<String>,
|
||||||
|
"max_quantity": 100,
|
||||||
|
"min_quantity": 1,
|
||||||
|
"price_list_id": "pl_no_customer_groups",
|
||||||
|
"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": Any<String>,
|
||||||
|
"max_quantity": 500,
|
||||||
|
"min_quantity": 101,
|
||||||
|
"price_list_id": "pl_no_customer_groups",
|
||||||
|
"region_id": null,
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"variant_id": "test-variant",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"amount": 50,
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"currency_code": "usd",
|
||||||
|
"deleted_at": null,
|
||||||
|
"id": Any<String>,
|
||||||
|
"max_quantity": 1000,
|
||||||
|
"min_quantity": 501,
|
||||||
|
"price_list_id": "pl_no_customer_groups",
|
||||||
|
"region_id": null,
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"variant_id": "test-variant",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"amount": 85,
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"currency_code": "usd",
|
||||||
|
"deleted_at": null,
|
||||||
|
"id": Any<String>,
|
||||||
|
"max_quantity": null,
|
||||||
|
"min_quantity": null,
|
||||||
|
"price_list_id": "pl_no_customer_groups",
|
||||||
|
"region_id": null,
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"variant_id": "test-variant_1",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"amount": 10,
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"currency_code": "usd",
|
||||||
|
"deleted_at": null,
|
||||||
|
"id": Any<String>,
|
||||||
|
"max_quantity": null,
|
||||||
|
"min_quantity": null,
|
||||||
|
"price_list_id": "pl_no_customer_groups",
|
||||||
|
"region_id": null,
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"variant_id": "test-variant",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"starts_at": "2022-09-01T00:00:00.000Z",
|
||||||
|
"status": "draft",
|
||||||
|
"type": "sale",
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`/admin/price-lists POST /admin/price-lists/:id updates the amount and currency of a price in the price list 1`] = `
|
||||||
|
Object {
|
||||||
|
"amount": 250,
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"currency_code": "eur",
|
||||||
|
"deleted_at": null,
|
||||||
|
"id": "ma_test_1",
|
||||||
|
"max_quantity": 100,
|
||||||
|
"min_quantity": 1,
|
||||||
|
"price_list_id": "pl_no_customer_groups",
|
||||||
|
"region_id": null,
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"variant_id": "test-variant",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`/admin/price-lists POST /admin/price-lists/:id/prices/batch Adds a batch of new prices to a price list overriding existing prices 1`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"amount": 45,
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"currency_code": "usd",
|
||||||
|
"deleted_at": null,
|
||||||
|
"id": Any<String>,
|
||||||
|
"max_quantity": 2000,
|
||||||
|
"min_quantity": 1001,
|
||||||
|
"price_list_id": "pl_no_customer_groups",
|
||||||
|
"region_id": null,
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"variant_id": "test-variant",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"amount": 35,
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"currency_code": "usd",
|
||||||
|
"deleted_at": null,
|
||||||
|
"id": Any<String>,
|
||||||
|
"max_quantity": 3000,
|
||||||
|
"min_quantity": 2001,
|
||||||
|
"price_list_id": "pl_no_customer_groups",
|
||||||
|
"region_id": null,
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"variant_id": "test-variant",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"amount": 25,
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"currency_code": "usd",
|
||||||
|
"deleted_at": null,
|
||||||
|
"id": Any<String>,
|
||||||
|
"max_quantity": 4000,
|
||||||
|
"min_quantity": 3001,
|
||||||
|
"price_list_id": "pl_no_customer_groups",
|
||||||
|
"region_id": null,
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"variant_id": "test-variant",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`/admin/price-lists POST /admin/price-lists/:id/prices/batch Adds a batch of new prices to a price list without overriding existing prices 1`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"amount": 100,
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"currency_code": "usd",
|
||||||
|
"deleted_at": null,
|
||||||
|
"id": Any<String>,
|
||||||
|
"max_quantity": 100,
|
||||||
|
"min_quantity": 1,
|
||||||
|
"price_list_id": "pl_no_customer_groups",
|
||||||
|
"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": Any<String>,
|
||||||
|
"max_quantity": 500,
|
||||||
|
"min_quantity": 101,
|
||||||
|
"price_list_id": "pl_no_customer_groups",
|
||||||
|
"region_id": null,
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"variant_id": "test-variant",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"amount": 50,
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"currency_code": "usd",
|
||||||
|
"deleted_at": null,
|
||||||
|
"id": Any<String>,
|
||||||
|
"max_quantity": 1000,
|
||||||
|
"min_quantity": 501,
|
||||||
|
"price_list_id": "pl_no_customer_groups",
|
||||||
|
"region_id": null,
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"variant_id": "test-variant",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"amount": 45,
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"currency_code": "usd",
|
||||||
|
"deleted_at": null,
|
||||||
|
"id": Any<String>,
|
||||||
|
"max_quantity": 2000,
|
||||||
|
"min_quantity": 1001,
|
||||||
|
"price_list_id": "pl_no_customer_groups",
|
||||||
|
"region_id": null,
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"variant_id": "test-variant",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"amount": 35,
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"currency_code": "usd",
|
||||||
|
"deleted_at": null,
|
||||||
|
"id": Any<String>,
|
||||||
|
"max_quantity": 3000,
|
||||||
|
"min_quantity": 2001,
|
||||||
|
"price_list_id": "pl_no_customer_groups",
|
||||||
|
"region_id": null,
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"variant_id": "test-variant",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"amount": 25,
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"currency_code": "usd",
|
||||||
|
"deleted_at": null,
|
||||||
|
"id": Any<String>,
|
||||||
|
"max_quantity": 4000,
|
||||||
|
"min_quantity": 3001,
|
||||||
|
"price_list_id": "pl_no_customer_groups",
|
||||||
|
"region_id": null,
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"variant_id": "test-variant",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
@@ -110,8 +110,10 @@ Array [
|
|||||||
"currency_code": "usd",
|
"currency_code": "usd",
|
||||||
"deleted_at": null,
|
"deleted_at": null,
|
||||||
"id": StringMatching /\\^test-price\\*/,
|
"id": StringMatching /\\^test-price\\*/,
|
||||||
|
"max_quantity": null,
|
||||||
|
"min_quantity": null,
|
||||||
|
"price_list_id": null,
|
||||||
"region_id": null,
|
"region_id": null,
|
||||||
"sale_amount": null,
|
|
||||||
"updated_at": Any<String>,
|
"updated_at": Any<String>,
|
||||||
"variant_id": StringMatching /\\^test-variant\\*/,
|
"variant_id": StringMatching /\\^test-variant\\*/,
|
||||||
},
|
},
|
||||||
@@ -159,8 +161,10 @@ Array [
|
|||||||
"currency_code": "usd",
|
"currency_code": "usd",
|
||||||
"deleted_at": null,
|
"deleted_at": null,
|
||||||
"id": StringMatching /\\^test-price\\*/,
|
"id": StringMatching /\\^test-price\\*/,
|
||||||
|
"max_quantity": null,
|
||||||
|
"min_quantity": null,
|
||||||
|
"price_list_id": null,
|
||||||
"region_id": null,
|
"region_id": null,
|
||||||
"sale_amount": null,
|
|
||||||
"updated_at": Any<String>,
|
"updated_at": Any<String>,
|
||||||
"variant_id": StringMatching /\\^test-variant\\*/,
|
"variant_id": StringMatching /\\^test-variant\\*/,
|
||||||
},
|
},
|
||||||
@@ -208,8 +212,10 @@ Array [
|
|||||||
"currency_code": "usd",
|
"currency_code": "usd",
|
||||||
"deleted_at": null,
|
"deleted_at": null,
|
||||||
"id": StringMatching /\\^test-price\\*/,
|
"id": StringMatching /\\^test-price\\*/,
|
||||||
|
"max_quantity": null,
|
||||||
|
"min_quantity": null,
|
||||||
|
"price_list_id": null,
|
||||||
"region_id": null,
|
"region_id": null,
|
||||||
"sale_amount": null,
|
|
||||||
"updated_at": Any<String>,
|
"updated_at": Any<String>,
|
||||||
"variant_id": StringMatching /\\^test-variant\\*/,
|
"variant_id": StringMatching /\\^test-variant\\*/,
|
||||||
},
|
},
|
||||||
@@ -315,8 +321,10 @@ Array [
|
|||||||
"currency_code": "usd",
|
"currency_code": "usd",
|
||||||
"deleted_at": null,
|
"deleted_at": null,
|
||||||
"id": StringMatching /\\^test-price\\*/,
|
"id": StringMatching /\\^test-price\\*/,
|
||||||
|
"max_quantity": null,
|
||||||
|
"min_quantity": null,
|
||||||
|
"price_list_id": null,
|
||||||
"region_id": null,
|
"region_id": null,
|
||||||
"sale_amount": null,
|
|
||||||
"updated_at": Any<String>,
|
"updated_at": Any<String>,
|
||||||
"variant_id": StringMatching /\\^test-variant\\*/,
|
"variant_id": StringMatching /\\^test-variant\\*/,
|
||||||
},
|
},
|
||||||
@@ -364,8 +372,10 @@ Array [
|
|||||||
"currency_code": "usd",
|
"currency_code": "usd",
|
||||||
"deleted_at": null,
|
"deleted_at": null,
|
||||||
"id": StringMatching /\\^test-price\\*/,
|
"id": StringMatching /\\^test-price\\*/,
|
||||||
|
"max_quantity": null,
|
||||||
|
"min_quantity": null,
|
||||||
|
"price_list_id": null,
|
||||||
"region_id": "test-region",
|
"region_id": "test-region",
|
||||||
"sale_amount": null,
|
|
||||||
"updated_at": Any<String>,
|
"updated_at": Any<String>,
|
||||||
"variant_id": StringMatching /\\^test-variant\\*/,
|
"variant_id": StringMatching /\\^test-variant\\*/,
|
||||||
},
|
},
|
||||||
@@ -561,8 +571,10 @@ Array [
|
|||||||
"currency_code": "usd",
|
"currency_code": "usd",
|
||||||
"deleted_at": null,
|
"deleted_at": null,
|
||||||
"id": Any<String>,
|
"id": Any<String>,
|
||||||
|
"max_quantity": null,
|
||||||
|
"min_quantity": null,
|
||||||
|
"price_list_id": null,
|
||||||
"region_id": null,
|
"region_id": null,
|
||||||
"sale_amount": null,
|
|
||||||
"updated_at": Any<String>,
|
"updated_at": Any<String>,
|
||||||
"variant_id": StringMatching /\\^variant_\\*/,
|
"variant_id": StringMatching /\\^variant_\\*/,
|
||||||
},
|
},
|
||||||
@@ -581,3 +593,318 @@ Array [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`/admin/products POST /admin/products creates a product 1`] = `
|
||||||
|
Object {
|
||||||
|
"collection": Object {
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"deleted_at": null,
|
||||||
|
"handle": "test-collection",
|
||||||
|
"id": "test-collection",
|
||||||
|
"metadata": null,
|
||||||
|
"title": "Test collection",
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
},
|
||||||
|
"collection_id": "test-collection",
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"deleted_at": null,
|
||||||
|
"description": "test-product-description",
|
||||||
|
"discountable": true,
|
||||||
|
"external_id": null,
|
||||||
|
"handle": "test",
|
||||||
|
"height": null,
|
||||||
|
"hs_code": null,
|
||||||
|
"id": StringMatching /\\^prod_\\*/,
|
||||||
|
"images": Array [
|
||||||
|
Object {
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"deleted_at": null,
|
||||||
|
"id": Any<String>,
|
||||||
|
"metadata": null,
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"url": "test-image.png",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"deleted_at": null,
|
||||||
|
"id": Any<String>,
|
||||||
|
"metadata": null,
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"url": "test-image-2.png",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"is_giftcard": false,
|
||||||
|
"length": null,
|
||||||
|
"material": null,
|
||||||
|
"metadata": null,
|
||||||
|
"mid_code": null,
|
||||||
|
"options": Array [
|
||||||
|
Object {
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"deleted_at": null,
|
||||||
|
"id": StringMatching /\\^opt_\\*/,
|
||||||
|
"metadata": null,
|
||||||
|
"product_id": StringMatching /\\^prod_\\*/,
|
||||||
|
"title": "size",
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"deleted_at": null,
|
||||||
|
"id": StringMatching /\\^opt_\\*/,
|
||||||
|
"metadata": null,
|
||||||
|
"product_id": StringMatching /\\^prod_\\*/,
|
||||||
|
"title": "color",
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"origin_country": null,
|
||||||
|
"profile_id": StringMatching /\\^sp_\\*/,
|
||||||
|
"status": "draft",
|
||||||
|
"subtitle": null,
|
||||||
|
"tags": Array [
|
||||||
|
Object {
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"deleted_at": null,
|
||||||
|
"id": Any<String>,
|
||||||
|
"metadata": null,
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"value": "123",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"deleted_at": null,
|
||||||
|
"id": Any<String>,
|
||||||
|
"metadata": null,
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"value": "456",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"thumbnail": "test-image.png",
|
||||||
|
"title": "Test",
|
||||||
|
"type": Object {
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"deleted_at": null,
|
||||||
|
"id": "test-type",
|
||||||
|
"metadata": null,
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"value": "test-type",
|
||||||
|
},
|
||||||
|
"type_id": "test-type",
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"variants": Array [
|
||||||
|
Object {
|
||||||
|
"allow_backorder": false,
|
||||||
|
"barcode": null,
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"deleted_at": null,
|
||||||
|
"ean": null,
|
||||||
|
"height": null,
|
||||||
|
"hs_code": null,
|
||||||
|
"id": StringMatching /\\^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": StringMatching /\\^optval_\\*/,
|
||||||
|
"metadata": null,
|
||||||
|
"option_id": StringMatching /\\^opt_\\*/,
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"value": "large",
|
||||||
|
"variant_id": StringMatching /\\^variant_\\*/,
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"deleted_at": null,
|
||||||
|
"id": StringMatching /\\^optval_\\*/,
|
||||||
|
"metadata": null,
|
||||||
|
"option_id": StringMatching /\\^opt_\\*/,
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"value": "green",
|
||||||
|
"variant_id": StringMatching /\\^variant_\\*/,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"origin_country": null,
|
||||||
|
"prices": Array [
|
||||||
|
Object {
|
||||||
|
"amount": 100,
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"currency_code": "usd",
|
||||||
|
"deleted_at": null,
|
||||||
|
"id": StringMatching /\\^ma_\\*/,
|
||||||
|
"max_quantity": null,
|
||||||
|
"min_quantity": null,
|
||||||
|
"price_list_id": null,
|
||||||
|
"region_id": null,
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"variant_id": StringMatching /\\^variant_\\*/,
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"amount": 45,
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"currency_code": "eur",
|
||||||
|
"deleted_at": null,
|
||||||
|
"id": StringMatching /\\^ma_\\*/,
|
||||||
|
"max_quantity": null,
|
||||||
|
"min_quantity": null,
|
||||||
|
"price_list_id": null,
|
||||||
|
"region_id": null,
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"variant_id": StringMatching /\\^variant_\\*/,
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"amount": 30,
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"currency_code": "dkk",
|
||||||
|
"deleted_at": null,
|
||||||
|
"id": StringMatching /\\^ma_\\*/,
|
||||||
|
"max_quantity": null,
|
||||||
|
"min_quantity": null,
|
||||||
|
"price_list_id": null,
|
||||||
|
"region_id": null,
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"variant_id": StringMatching /\\^variant_\\*/,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"product_id": StringMatching /\\^prod_\\*/,
|
||||||
|
"sku": null,
|
||||||
|
"title": "Test variant",
|
||||||
|
"upc": null,
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"weight": null,
|
||||||
|
"width": null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"weight": null,
|
||||||
|
"width": null,
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`/admin/products POST /admin/products updates a product (update prices, tags, update status, delete collection, delete type, replaces images) 1`] = `
|
||||||
|
Object {
|
||||||
|
"collection": null,
|
||||||
|
"collection_id": null,
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"deleted_at": null,
|
||||||
|
"description": "test-product-description",
|
||||||
|
"discountable": true,
|
||||||
|
"external_id": null,
|
||||||
|
"handle": "test-product",
|
||||||
|
"height": null,
|
||||||
|
"hs_code": null,
|
||||||
|
"id": "test-product",
|
||||||
|
"images": Array [
|
||||||
|
Object {
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"deleted_at": null,
|
||||||
|
"id": StringMatching /\\^img_\\*/,
|
||||||
|
"metadata": null,
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"url": "test-image-2.png",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"is_giftcard": false,
|
||||||
|
"length": null,
|
||||||
|
"material": null,
|
||||||
|
"metadata": null,
|
||||||
|
"mid_code": null,
|
||||||
|
"options": Array [
|
||||||
|
Object {
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"deleted_at": null,
|
||||||
|
"id": "test-option",
|
||||||
|
"metadata": null,
|
||||||
|
"product_id": "test-product",
|
||||||
|
"title": "test-option",
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"origin_country": null,
|
||||||
|
"profile_id": StringMatching /\\^sp_\\*/,
|
||||||
|
"status": "published",
|
||||||
|
"subtitle": null,
|
||||||
|
"tags": Array [
|
||||||
|
Object {
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"deleted_at": null,
|
||||||
|
"id": "tag1",
|
||||||
|
"metadata": null,
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"value": "123",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"thumbnail": "test-image-2.png",
|
||||||
|
"title": "Test product",
|
||||||
|
"type": Object {
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"deleted_at": null,
|
||||||
|
"id": StringMatching /\\^ptyp_\\*/,
|
||||||
|
"metadata": null,
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"value": "test-type-2",
|
||||||
|
},
|
||||||
|
"type_id": StringMatching /\\^ptyp_\\*/,
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"variants": Array [
|
||||||
|
Object {
|
||||||
|
"allow_backorder": false,
|
||||||
|
"barcode": "test-barcode",
|
||||||
|
"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,
|
||||||
|
"prices": Array [
|
||||||
|
Object {
|
||||||
|
"amount": 75,
|
||||||
|
"created_at": Any<String>,
|
||||||
|
"currency_code": "usd",
|
||||||
|
"deleted_at": null,
|
||||||
|
"id": "test-price",
|
||||||
|
"max_quantity": null,
|
||||||
|
"min_quantity": null,
|
||||||
|
"price_list_id": null,
|
||||||
|
"region_id": null,
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"variant_id": "test-variant",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"product_id": "test-product",
|
||||||
|
"sku": "test-sku",
|
||||||
|
"title": "Test variant",
|
||||||
|
"upc": "test-upc",
|
||||||
|
"updated_at": Any<String>,
|
||||||
|
"weight": null,
|
||||||
|
"width": null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"weight": null,
|
||||||
|
"width": null,
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,758 @@
|
|||||||
|
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 customerSeeder = require("../../helpers/customer-seeder")
|
||||||
|
const priceListSeeder = require("../../helpers/price-list-seeder")
|
||||||
|
const productSeeder = require("../../helpers/product-seeder")
|
||||||
|
|
||||||
|
jest.setTimeout(30000)
|
||||||
|
|
||||||
|
describe("/admin/price-lists", () => {
|
||||||
|
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("POST /admin/price-list", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
try {
|
||||||
|
await adminSeeder(dbConnection)
|
||||||
|
await customerSeeder(dbConnection)
|
||||||
|
await productSeeder(dbConnection)
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
const db = useDb()
|
||||||
|
await db.teardown()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("creates a price list", async () => {
|
||||||
|
const api = useApi()
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
name: "VIP Summer sale",
|
||||||
|
description: "Summer sale for VIP customers. 25% off selected items.",
|
||||||
|
type: "sale",
|
||||||
|
status: "active",
|
||||||
|
starts_at: "2022-07-01T00:00:00.000Z",
|
||||||
|
ends_at: "2022-07-31T00:00:00.000Z",
|
||||||
|
customer_groups: [
|
||||||
|
{
|
||||||
|
id: "customer-group-1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
prices: [
|
||||||
|
{
|
||||||
|
amount: 85,
|
||||||
|
currency_code: "usd",
|
||||||
|
variant_id: "test-variant",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await api
|
||||||
|
.post("/admin/price-lists", payload, {
|
||||||
|
headers: {
|
||||||
|
Authorization: "Bearer test_token",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.warn(err.response.data)
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.price_list).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
name: "VIP Summer sale",
|
||||||
|
description: "Summer sale for VIP customers. 25% off selected items.",
|
||||||
|
type: "sale",
|
||||||
|
status: "active",
|
||||||
|
starts_at: "2022-07-01T00:00:00.000Z",
|
||||||
|
ends_at: "2022-07-31T00:00:00.000Z",
|
||||||
|
customer_groups: [
|
||||||
|
expect.objectContaining({
|
||||||
|
id: "customer-group-1",
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
prices: [
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
amount: 85,
|
||||||
|
currency_code: "usd",
|
||||||
|
variant_id: "test-variant",
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("GET /admin/price-lists", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
try {
|
||||||
|
await adminSeeder(dbConnection)
|
||||||
|
await productSeeder(dbConnection)
|
||||||
|
await priceListSeeder(dbConnection)
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
const db = useDb()
|
||||||
|
await db.teardown()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns a price list by :id", async () => {
|
||||||
|
const api = useApi()
|
||||||
|
|
||||||
|
const response = await api
|
||||||
|
.get("/admin/price-lists/pl_no_customer_groups", {
|
||||||
|
headers: {
|
||||||
|
Authorization: "Bearer test_token",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.warn(err.response.data)
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.price_list).toMatchSnapshot({
|
||||||
|
id: expect.any(String),
|
||||||
|
name: "VIP winter sale",
|
||||||
|
description: "Winter sale for VIP customers. 25% off selected items.",
|
||||||
|
type: "sale",
|
||||||
|
status: "active",
|
||||||
|
starts_at: "2022-07-01T00:00:00.000Z",
|
||||||
|
ends_at: "2022-07-31T00:00:00.000Z",
|
||||||
|
prices: [
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
amount: 100,
|
||||||
|
currency_code: "usd",
|
||||||
|
min_quantity: 1,
|
||||||
|
max_quantity: 100,
|
||||||
|
variant_id: "test-variant",
|
||||||
|
price_list_id: "pl_no_customer_groups",
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
amount: 80,
|
||||||
|
currency_code: "usd",
|
||||||
|
min_quantity: 101,
|
||||||
|
max_quantity: 500,
|
||||||
|
variant_id: "test-variant",
|
||||||
|
price_list_id: "pl_no_customer_groups",
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
amount: 50,
|
||||||
|
currency_code: "usd",
|
||||||
|
min_quantity: 501,
|
||||||
|
max_quantity: 1000,
|
||||||
|
variant_id: "test-variant",
|
||||||
|
price_list_id: "pl_no_customer_groups",
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns a list of price lists", async () => {
|
||||||
|
const api = useApi()
|
||||||
|
|
||||||
|
const response = await api
|
||||||
|
.get("/admin/price-lists", {
|
||||||
|
headers: {
|
||||||
|
Authorization: "Bearer test_token",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.warn(err.response.data)
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.price_lists).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: "pl_no_customer_groups",
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("POST /admin/price-lists/:id", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
try {
|
||||||
|
await adminSeeder(dbConnection)
|
||||||
|
await customerSeeder(dbConnection)
|
||||||
|
await productSeeder(dbConnection)
|
||||||
|
await priceListSeeder(dbConnection)
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
const db = useDb()
|
||||||
|
await db.teardown()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("updates a price list", async () => {
|
||||||
|
const api = useApi()
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
name: "Loyalty Reward - Winter Sale",
|
||||||
|
description: "Winter sale for our most loyal customers",
|
||||||
|
type: "sale",
|
||||||
|
status: "draft",
|
||||||
|
starts_at: "2022-09-01T00:00:00.000Z",
|
||||||
|
ends_at: "2022-12-31T00:00:00.000Z",
|
||||||
|
customer_groups: [
|
||||||
|
{
|
||||||
|
id: "customer-group-1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
prices: [
|
||||||
|
{
|
||||||
|
amount: 85,
|
||||||
|
currency_code: "usd",
|
||||||
|
variant_id: "test-variant_1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
amount: 10,
|
||||||
|
currency_code: "usd",
|
||||||
|
variant_id: "test-variant",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await api
|
||||||
|
.post("/admin/price-lists/pl_no_customer_groups", payload, {
|
||||||
|
headers: {
|
||||||
|
Authorization: "Bearer test_token",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.warn(err.response.data)
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.price_list).toMatchSnapshot({
|
||||||
|
id: "pl_no_customer_groups",
|
||||||
|
name: "Loyalty Reward - Winter Sale",
|
||||||
|
description: "Winter sale for our most loyal customers",
|
||||||
|
type: "sale",
|
||||||
|
status: "draft",
|
||||||
|
starts_at: "2022-09-01T00:00:00.000Z",
|
||||||
|
ends_at: "2022-12-31T00:00:00.000Z",
|
||||||
|
prices: [
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
amount: 100,
|
||||||
|
currency_code: "usd",
|
||||||
|
min_quantity: 1,
|
||||||
|
max_quantity: 100,
|
||||||
|
variant_id: "test-variant",
|
||||||
|
price_list_id: "pl_no_customer_groups",
|
||||||
|
region_id: null,
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
amount: 80,
|
||||||
|
currency_code: "usd",
|
||||||
|
min_quantity: 101,
|
||||||
|
max_quantity: 500,
|
||||||
|
variant_id: "test-variant",
|
||||||
|
price_list_id: "pl_no_customer_groups",
|
||||||
|
region_id: null,
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
amount: 50,
|
||||||
|
currency_code: "usd",
|
||||||
|
min_quantity: 501,
|
||||||
|
max_quantity: 1000,
|
||||||
|
variant_id: "test-variant",
|
||||||
|
price_list_id: "pl_no_customer_groups",
|
||||||
|
region_id: null,
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
amount: 85,
|
||||||
|
currency_code: "usd",
|
||||||
|
variant_id: "test-variant_1",
|
||||||
|
price_list_id: "pl_no_customer_groups",
|
||||||
|
min_quantity: null,
|
||||||
|
max_quantity: null,
|
||||||
|
region_id: null,
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
deleted_at: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
amount: 10,
|
||||||
|
currency_code: "usd",
|
||||||
|
variant_id: "test-variant",
|
||||||
|
price_list_id: "pl_no_customer_groups",
|
||||||
|
min_quantity: null,
|
||||||
|
max_quantity: null,
|
||||||
|
region_id: null,
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
deleted_at: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
customer_groups: [
|
||||||
|
{
|
||||||
|
id: "customer-group-1",
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("updates the amount and currency of a price in the price list", async () => {
|
||||||
|
const api = useApi()
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
prices: [
|
||||||
|
{
|
||||||
|
id: "ma_test_1",
|
||||||
|
amount: 250,
|
||||||
|
currency_code: "eur",
|
||||||
|
variant_id: "test-variant",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await api
|
||||||
|
.post("/admin/price-lists/pl_no_customer_groups", payload, {
|
||||||
|
headers: {
|
||||||
|
Authorization: "Bearer test_token",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.warn(err.response.data)
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
|
||||||
|
const updatedPrice = response.data.price_list.prices.find(
|
||||||
|
(p) => p.id === "ma_test_1"
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(updatedPrice).toMatchSnapshot({
|
||||||
|
id: "ma_test_1",
|
||||||
|
amount: 250,
|
||||||
|
currency_code: "eur",
|
||||||
|
min_quantity: 1,
|
||||||
|
max_quantity: 100,
|
||||||
|
variant_id: "test-variant",
|
||||||
|
price_list_id: "pl_no_customer_groups",
|
||||||
|
region_id: null,
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("POST /admin/price-lists/:id/prices/batch", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
try {
|
||||||
|
await adminSeeder(dbConnection)
|
||||||
|
await productSeeder(dbConnection)
|
||||||
|
await priceListSeeder(dbConnection)
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
const db = useDb()
|
||||||
|
await db.teardown()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Adds a batch of new prices to a price list without overriding existing prices", async () => {
|
||||||
|
const api = useApi()
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
prices: [
|
||||||
|
{
|
||||||
|
amount: 45,
|
||||||
|
currency_code: "usd",
|
||||||
|
variant_id: "test-variant",
|
||||||
|
min_quantity: 1001,
|
||||||
|
max_quantity: 2000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
amount: 35,
|
||||||
|
currency_code: "usd",
|
||||||
|
variant_id: "test-variant",
|
||||||
|
min_quantity: 2001,
|
||||||
|
max_quantity: 3000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
amount: 25,
|
||||||
|
currency_code: "usd",
|
||||||
|
variant_id: "test-variant",
|
||||||
|
min_quantity: 3001,
|
||||||
|
max_quantity: 4000,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await api
|
||||||
|
.post(
|
||||||
|
"/admin/price-lists/pl_no_customer_groups/prices/batch",
|
||||||
|
payload,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: "Bearer test_token",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch((err) => {
|
||||||
|
console.warn(err.response.data)
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.price_list.prices.length).toEqual(6)
|
||||||
|
expect(response.data.price_list.prices).toMatchSnapshot([
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
price_list_id: "pl_no_customer_groups",
|
||||||
|
amount: 100,
|
||||||
|
currency_code: "usd",
|
||||||
|
min_quantity: 1,
|
||||||
|
max_quantity: 100,
|
||||||
|
variant_id: "test-variant",
|
||||||
|
price_list_id: "pl_no_customer_groups",
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
price_list_id: "pl_no_customer_groups",
|
||||||
|
amount: 80,
|
||||||
|
currency_code: "usd",
|
||||||
|
min_quantity: 101,
|
||||||
|
max_quantity: 500,
|
||||||
|
variant_id: "test-variant",
|
||||||
|
price_list_id: "pl_no_customer_groups",
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
price_list_id: "pl_no_customer_groups",
|
||||||
|
amount: 50,
|
||||||
|
currency_code: "usd",
|
||||||
|
min_quantity: 501,
|
||||||
|
max_quantity: 1000,
|
||||||
|
variant_id: "test-variant",
|
||||||
|
price_list_id: "pl_no_customer_groups",
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
price_list_id: "pl_no_customer_groups",
|
||||||
|
amount: 45,
|
||||||
|
currency_code: "usd",
|
||||||
|
variant_id: "test-variant",
|
||||||
|
min_quantity: 1001,
|
||||||
|
max_quantity: 2000,
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
price_list_id: "pl_no_customer_groups",
|
||||||
|
amount: 35,
|
||||||
|
currency_code: "usd",
|
||||||
|
variant_id: "test-variant",
|
||||||
|
min_quantity: 2001,
|
||||||
|
max_quantity: 3000,
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
price_list_id: "pl_no_customer_groups",
|
||||||
|
amount: 25,
|
||||||
|
currency_code: "usd",
|
||||||
|
variant_id: "test-variant",
|
||||||
|
min_quantity: 3001,
|
||||||
|
max_quantity: 4000,
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Adds a batch of new prices to a price list overriding existing prices", async () => {
|
||||||
|
const api = useApi()
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
prices: [
|
||||||
|
{
|
||||||
|
amount: 45,
|
||||||
|
currency_code: "usd",
|
||||||
|
variant_id: "test-variant",
|
||||||
|
min_quantity: 1001,
|
||||||
|
max_quantity: 2000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
amount: 35,
|
||||||
|
currency_code: "usd",
|
||||||
|
variant_id: "test-variant",
|
||||||
|
min_quantity: 2001,
|
||||||
|
max_quantity: 3000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
amount: 25,
|
||||||
|
currency_code: "usd",
|
||||||
|
variant_id: "test-variant",
|
||||||
|
min_quantity: 3001,
|
||||||
|
max_quantity: 4000,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
override: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await api
|
||||||
|
.post(
|
||||||
|
"/admin/price-lists/pl_no_customer_groups/prices/batch",
|
||||||
|
payload,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: "Bearer test_token",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch((err) => {
|
||||||
|
console.warn(err.response.data)
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.price_list.prices.length).toEqual(3)
|
||||||
|
expect(response.data.price_list.prices).toMatchSnapshot([
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
price_list_id: "pl_no_customer_groups",
|
||||||
|
amount: 45,
|
||||||
|
currency_code: "usd",
|
||||||
|
variant_id: "test-variant",
|
||||||
|
min_quantity: 1001,
|
||||||
|
max_quantity: 2000,
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
price_list_id: "pl_no_customer_groups",
|
||||||
|
amount: 35,
|
||||||
|
currency_code: "usd",
|
||||||
|
variant_id: "test-variant",
|
||||||
|
min_quantity: 2001,
|
||||||
|
max_quantity: 3000,
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
price_list_id: "pl_no_customer_groups",
|
||||||
|
amount: 25,
|
||||||
|
currency_code: "usd",
|
||||||
|
variant_id: "test-variant",
|
||||||
|
min_quantity: 3001,
|
||||||
|
max_quantity: 4000,
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("DELETE /admin/price-lists/:id", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
try {
|
||||||
|
await adminSeeder(dbConnection)
|
||||||
|
await productSeeder(dbConnection)
|
||||||
|
await priceListSeeder(dbConnection)
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
const db = useDb()
|
||||||
|
await db.teardown()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Deletes a price list", async () => {
|
||||||
|
const api = useApi()
|
||||||
|
|
||||||
|
const response = await api
|
||||||
|
.delete("/admin/price-lists/pl_no_customer_groups", {
|
||||||
|
headers: {
|
||||||
|
Authorization: "Bearer test_token",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.warn(err.response.data)
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data).toEqual({
|
||||||
|
id: "pl_no_customer_groups",
|
||||||
|
object: "price-list",
|
||||||
|
deleted: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("tests cascade on delete", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
try {
|
||||||
|
await adminSeeder(dbConnection)
|
||||||
|
await productSeeder(dbConnection)
|
||||||
|
await priceListSeeder(dbConnection)
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
const db = useDb()
|
||||||
|
await db.teardown()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Deletes a variant and ensures that prices associated with the variant are deleted from PriceList", async () => {
|
||||||
|
const api = useApi()
|
||||||
|
|
||||||
|
const deleteResponse = await api
|
||||||
|
.delete("/admin/products/test-product/variants/test-variant", {
|
||||||
|
headers: {
|
||||||
|
Authorization: "Bearer test_token",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.warn(err.response.data)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const response = await api.get(
|
||||||
|
"/admin/price-lists/pl_no_customer_groups",
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: "Bearer test_token",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.price_list.prices.length).toEqual(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("DELETE /admin/price-lists/:id/prices/batch", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
try {
|
||||||
|
await adminSeeder(dbConnection)
|
||||||
|
await productSeeder(dbConnection)
|
||||||
|
await priceListSeeder(dbConnection)
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
const db = useDb()
|
||||||
|
await db.teardown()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Deletes several prices associated with a price list", async () => {
|
||||||
|
const api = useApi()
|
||||||
|
|
||||||
|
const response = await api
|
||||||
|
.delete("/admin/price-lists/pl_no_customer_groups/prices/batch", {
|
||||||
|
headers: {
|
||||||
|
Authorization: "Bearer test_token",
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
price_ids: ["ma_test_1", "ma_test_2"],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.warn(err.response.data)
|
||||||
|
})
|
||||||
|
|
||||||
|
const getPriceListResponse = await api
|
||||||
|
.get("/admin/price-lists/pl_no_customer_groups", {
|
||||||
|
headers: {
|
||||||
|
Authorization: "Bearer test_token",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.warn(err.response.data)
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data).toEqual({
|
||||||
|
ids: ["ma_test_1", "ma_test_2"],
|
||||||
|
object: "money-amount",
|
||||||
|
deleted: true,
|
||||||
|
})
|
||||||
|
expect(getPriceListResponse.data.price_list.prices.length).toEqual(1)
|
||||||
|
expect(getPriceListResponse.data.price_list.prices[0].id).toEqual(
|
||||||
|
"ma_test_3"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -7,6 +7,7 @@ const { initDb, useDb } = require("../../../helpers/use-db")
|
|||||||
const adminSeeder = require("../../helpers/admin-seeder")
|
const adminSeeder = require("../../helpers/admin-seeder")
|
||||||
const productSeeder = require("../../helpers/product-seeder")
|
const productSeeder = require("../../helpers/product-seeder")
|
||||||
const { ProductVariant } = require("@medusajs/medusa")
|
const { ProductVariant } = require("@medusajs/medusa")
|
||||||
|
const priceListSeeder = require("../../helpers/price-list-seeder")
|
||||||
|
|
||||||
jest.setTimeout(50000)
|
jest.setTimeout(50000)
|
||||||
|
|
||||||
@@ -773,7 +774,20 @@ describe("/admin/products", () => {
|
|||||||
{
|
{
|
||||||
title: "Test variant",
|
title: "Test variant",
|
||||||
inventory_quantity: 10,
|
inventory_quantity: 10,
|
||||||
prices: [{ currency_code: "usd", amount: 100 }],
|
prices: [
|
||||||
|
{
|
||||||
|
currency_code: "usd",
|
||||||
|
amount: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currency_code: "eur",
|
||||||
|
amount: 45,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currency_code: "dkk",
|
||||||
|
amount: 30,
|
||||||
|
},
|
||||||
|
],
|
||||||
options: [{ value: "large" }, { value: "green" }],
|
options: [{ value: "large" }, { value: "green" }],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -790,66 +804,126 @@ describe("/admin/products", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
expect(response.status).toEqual(200)
|
expect(response.status).toEqual(200)
|
||||||
expect(response.data.product).toEqual(
|
expect(response.data.product).toMatchSnapshot({
|
||||||
expect.objectContaining({
|
id: expect.stringMatching(/^prod_*/),
|
||||||
title: "Test",
|
title: "Test",
|
||||||
discountable: true,
|
discountable: true,
|
||||||
is_giftcard: false,
|
is_giftcard: false,
|
||||||
handle: "test",
|
handle: "test",
|
||||||
status: "draft",
|
status: "draft",
|
||||||
images: expect.arrayContaining([
|
created_at: expect.any(String),
|
||||||
expect.objectContaining({
|
updated_at: expect.any(String),
|
||||||
url: "test-image.png",
|
profile_id: expect.stringMatching(/^sp_*/),
|
||||||
}),
|
images: [
|
||||||
expect.objectContaining({
|
{
|
||||||
url: "test-image-2.png",
|
id: expect.any(String),
|
||||||
}),
|
url: "test-image.png",
|
||||||
]),
|
created_at: expect.any(String),
|
||||||
thumbnail: "test-image.png",
|
updated_at: expect.any(String),
|
||||||
tags: [
|
},
|
||||||
expect.objectContaining({
|
{
|
||||||
value: "123",
|
id: expect.any(String),
|
||||||
}),
|
url: "test-image-2.png",
|
||||||
expect.objectContaining({
|
created_at: expect.any(String),
|
||||||
value: "456",
|
updated_at: expect.any(String),
|
||||||
}),
|
},
|
||||||
],
|
],
|
||||||
type: expect.objectContaining({
|
thumbnail: "test-image.png",
|
||||||
value: "test-type",
|
tags: [
|
||||||
}),
|
{
|
||||||
collection: expect.objectContaining({
|
id: expect.any(String),
|
||||||
id: "test-collection",
|
value: "123",
|
||||||
title: "Test collection",
|
created_at: expect.any(String),
|
||||||
}),
|
updated_at: expect.any(String),
|
||||||
options: [
|
},
|
||||||
expect.objectContaining({
|
{
|
||||||
title: "size",
|
id: expect.any(String),
|
||||||
}),
|
value: "456",
|
||||||
expect.objectContaining({
|
created_at: expect.any(String),
|
||||||
title: "color",
|
updated_at: expect.any(String),
|
||||||
}),
|
},
|
||||||
],
|
],
|
||||||
variants: [
|
type: {
|
||||||
expect.objectContaining({
|
value: "test-type",
|
||||||
title: "Test variant",
|
created_at: expect.any(String),
|
||||||
prices: [
|
updated_at: expect.any(String),
|
||||||
expect.objectContaining({
|
},
|
||||||
currency_code: "usd",
|
collection: {
|
||||||
amount: 100,
|
id: "test-collection",
|
||||||
}),
|
title: "Test collection",
|
||||||
],
|
created_at: expect.any(String),
|
||||||
options: [
|
updated_at: expect.any(String),
|
||||||
expect.objectContaining({
|
},
|
||||||
value: "large",
|
options: [
|
||||||
}),
|
{
|
||||||
expect.objectContaining({
|
id: expect.stringMatching(/^opt_*/),
|
||||||
value: "green",
|
product_id: expect.stringMatching(/^prod_*/),
|
||||||
}),
|
title: "size",
|
||||||
],
|
created_at: expect.any(String),
|
||||||
}),
|
updated_at: expect.any(String),
|
||||||
],
|
},
|
||||||
})
|
{
|
||||||
)
|
id: expect.stringMatching(/^opt_*/),
|
||||||
|
product_id: expect.stringMatching(/^prod_*/),
|
||||||
|
title: "color",
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
id: expect.stringMatching(/^variant_*/),
|
||||||
|
product_id: expect.stringMatching(/^prod_*/),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
created_at: expect.any(String),
|
||||||
|
title: "Test variant",
|
||||||
|
prices: [
|
||||||
|
{
|
||||||
|
id: expect.stringMatching(/^ma_*/),
|
||||||
|
currency_code: "usd",
|
||||||
|
amount: 100,
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
variant_id: expect.stringMatching(/^variant_*/),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: expect.stringMatching(/^ma_*/),
|
||||||
|
currency_code: "eur",
|
||||||
|
amount: 45,
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
variant_id: expect.stringMatching(/^variant_*/),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: expect.stringMatching(/^ma_*/),
|
||||||
|
currency_code: "dkk",
|
||||||
|
amount: 30,
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
variant_id: expect.stringMatching(/^variant_*/),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
value: "large",
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
variant_id: expect.stringMatching(/^variant_*/),
|
||||||
|
option_id: expect.stringMatching(/^opt_*/),
|
||||||
|
id: expect.stringMatching(/^optval_*/),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "green",
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
variant_id: expect.stringMatching(/^variant_*/),
|
||||||
|
option_id: expect.stringMatching(/^opt_*/),
|
||||||
|
id: expect.stringMatching(/^optval_*/),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("creates a product that is not discountable", async () => {
|
it("creates a product that is not discountable", async () => {
|
||||||
@@ -1006,8 +1080,7 @@ describe("/admin/products", () => {
|
|||||||
prices: [
|
prices: [
|
||||||
{
|
{
|
||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
amount: 100,
|
amount: 75,
|
||||||
sale_amount: 75,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -1030,36 +1103,92 @@ describe("/admin/products", () => {
|
|||||||
|
|
||||||
expect(response.status).toEqual(200)
|
expect(response.status).toEqual(200)
|
||||||
|
|
||||||
expect(response.data.product).toEqual(
|
expect(response.data.product).toMatchSnapshot({
|
||||||
expect.objectContaining({
|
id: "test-product",
|
||||||
images: expect.arrayContaining([
|
created_at: expect.any(String),
|
||||||
expect.objectContaining({
|
description: "test-product-description",
|
||||||
url: "test-image-2.png",
|
discountable: true,
|
||||||
}),
|
handle: "test-product",
|
||||||
]),
|
images: [
|
||||||
thumbnail: "test-image-2.png",
|
{
|
||||||
tags: [
|
created_at: expect.any(String),
|
||||||
expect.objectContaining({
|
deleted_at: null,
|
||||||
value: "123",
|
id: expect.stringMatching(/^img_*/),
|
||||||
}),
|
metadata: null,
|
||||||
],
|
updated_at: expect.any(String),
|
||||||
variants: [
|
url: "test-image-2.png",
|
||||||
expect.objectContaining({
|
},
|
||||||
prices: [
|
],
|
||||||
expect.objectContaining({
|
is_giftcard: false,
|
||||||
sale_amount: 75,
|
options: [
|
||||||
amount: 100,
|
{
|
||||||
}),
|
created_at: expect.any(String),
|
||||||
],
|
id: "test-option",
|
||||||
}),
|
product_id: "test-product",
|
||||||
],
|
title: "test-option",
|
||||||
status: "published",
|
updated_at: expect.any(String),
|
||||||
collection: null,
|
},
|
||||||
type: expect.objectContaining({
|
],
|
||||||
value: "test-type-2",
|
profile_id: expect.stringMatching(/^sp_*/),
|
||||||
}),
|
status: "published",
|
||||||
})
|
tags: [
|
||||||
)
|
{
|
||||||
|
created_at: expect.any(String),
|
||||||
|
id: "tag1",
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
value: "123",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
thumbnail: "test-image-2.png",
|
||||||
|
title: "Test product",
|
||||||
|
type: {
|
||||||
|
created_at: expect.any(String),
|
||||||
|
id: expect.stringMatching(/^ptyp_*/),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
value: "test-type-2",
|
||||||
|
},
|
||||||
|
type_id: expect.stringMatching(/^ptyp_*/),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
allow_backorder: false,
|
||||||
|
barcode: "test-barcode",
|
||||||
|
created_at: expect.any(String),
|
||||||
|
ean: "test-ean",
|
||||||
|
id: "test-variant",
|
||||||
|
inventory_quantity: 10,
|
||||||
|
manage_inventory: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
created_at: expect.any(String),
|
||||||
|
deleted_at: null,
|
||||||
|
id: "test-variant-option",
|
||||||
|
metadata: null,
|
||||||
|
option_id: "test-option",
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
value: "Default variant",
|
||||||
|
variant_id: "test-variant",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
origin_country: null,
|
||||||
|
prices: [
|
||||||
|
{
|
||||||
|
amount: 75,
|
||||||
|
created_at: expect.any(String),
|
||||||
|
currency_code: "usd",
|
||||||
|
id: "test-price",
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
variant_id: "test-variant",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
product_id: "test-product",
|
||||||
|
sku: "test-sku",
|
||||||
|
title: "Test variant",
|
||||||
|
upc: "test-upc",
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("updates product (removes images when empty array included)", async () => {
|
it("updates product (removes images when empty array included)", async () => {
|
||||||
@@ -1189,11 +1318,12 @@ describe("/admin/products", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("updates a variant's prices", () => {
|
describe("updates a variant's default prices (ignores prices associated with a Price List)", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
try {
|
try {
|
||||||
await productSeeder(dbConnection)
|
await productSeeder(dbConnection)
|
||||||
await adminSeeder(dbConnection)
|
await adminSeeder(dbConnection)
|
||||||
|
await priceListSeeder(dbConnection)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
throw err
|
throw err
|
||||||
@@ -1205,7 +1335,7 @@ describe("/admin/products", () => {
|
|||||||
await db.teardown()
|
await db.teardown()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("successfully updates a variant's prices by changing an existing price (currency_code)", async () => {
|
it("successfully updates a variant's default prices by changing an existing price (currency_code)", async () => {
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
const data = {
|
const data = {
|
||||||
prices: [
|
prices: [
|
||||||
@@ -1239,6 +1369,33 @@ 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",
|
||||||
|
}),
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
@@ -1316,26 +1473,55 @@ describe("/admin/products", () => {
|
|||||||
|
|
||||||
expect(response.status).toEqual(200)
|
expect(response.status).toEqual(200)
|
||||||
|
|
||||||
expect(response.data).toEqual({
|
expect(response.data).toEqual(
|
||||||
product: expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: "test-product",
|
product: expect.objectContaining({
|
||||||
variants: expect.arrayContaining([
|
id: "test-product",
|
||||||
expect.objectContaining({
|
variants: expect.arrayContaining([
|
||||||
id: "test-variant",
|
expect.objectContaining({
|
||||||
prices: [
|
id: "test-variant",
|
||||||
expect.objectContaining({
|
prices: expect.arrayContaining([
|
||||||
amount: 100,
|
expect.objectContaining({
|
||||||
currency_code: "usd",
|
amount: 100,
|
||||||
}),
|
currency_code: "usd",
|
||||||
expect.objectContaining({
|
}),
|
||||||
amount: 4500,
|
expect.objectContaining({
|
||||||
currency_code: "eur",
|
amount: 4500,
|
||||||
}),
|
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",
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("successfully updates a variant's prices by replacing a price", async () => {
|
it("successfully updates a variant's prices by replacing a price", async () => {
|
||||||
@@ -1343,7 +1529,7 @@ describe("/admin/products", () => {
|
|||||||
const data = {
|
const data = {
|
||||||
prices: [
|
prices: [
|
||||||
{
|
{
|
||||||
currency_code: "eur",
|
currency_code: "usd",
|
||||||
amount: 4500,
|
amount: 4500,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -1362,14 +1548,43 @@ 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(
|
||||||
data.prices.length
|
4 // 3 prices from Price List + 1 default price
|
||||||
|
)
|
||||||
|
expect(response.data.product.variants[0].prices).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
amount: 4500,
|
||||||
|
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",
|
||||||
|
}),
|
||||||
|
])
|
||||||
)
|
)
|
||||||
expect(response.data.product.variants[0].prices).toEqual([
|
|
||||||
expect.objectContaining({
|
|
||||||
amount: 4500,
|
|
||||||
currency_code: "eur",
|
|
||||||
}),
|
|
||||||
])
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("successfully updates a variant's prices by deleting a price and adding another price", async () => {
|
it("successfully updates a variant's prices by deleting a price and adding another price", async () => {
|
||||||
@@ -1400,18 +1615,47 @@ 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(
|
||||||
data.prices.length
|
5 // 2 default prices + 3 prices from Price List
|
||||||
|
)
|
||||||
|
expect(response.data.product.variants[0].prices).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
amount: 8000,
|
||||||
|
currency_code: "dkk",
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
amount: 900,
|
||||||
|
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",
|
||||||
|
}),
|
||||||
|
])
|
||||||
)
|
)
|
||||||
expect(response.data.product.variants[0].prices).toEqual([
|
|
||||||
expect.objectContaining({
|
|
||||||
amount: 8000,
|
|
||||||
currency_code: "dkk",
|
|
||||||
}),
|
|
||||||
expect.objectContaining({
|
|
||||||
amount: 900,
|
|
||||||
currency_code: "eur",
|
|
||||||
}),
|
|
||||||
])
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("successfully updates a variant's prices by updating an existing price (using region_id) and adding another price", async () => {
|
it("successfully updates a variant's prices by updating an existing price (using region_id) and adding another price", async () => {
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ describe("/admin/store", () => {
|
|||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
const db = useDb()
|
const db = useDb()
|
||||||
db.teardown()
|
await db.teardown()
|
||||||
medusaProcess.kill()
|
await medusaProcess.kill()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("has created store with default currency", async () => {
|
it("has created store with default currency", async () => {
|
||||||
|
|||||||
@@ -37,8 +37,10 @@ Object {
|
|||||||
"currency_code": "usd",
|
"currency_code": "usd",
|
||||||
"deleted_at": null,
|
"deleted_at": null,
|
||||||
"id": "test-price",
|
"id": "test-price",
|
||||||
|
"max_quantity": null,
|
||||||
|
"min_quantity": null,
|
||||||
|
"price_list_id": null,
|
||||||
"region_id": null,
|
"region_id": null,
|
||||||
"sale_amount": null,
|
|
||||||
"updated_at": Any<String>,
|
"updated_at": Any<String>,
|
||||||
"variant_id": "test-variant",
|
"variant_id": "test-variant",
|
||||||
},
|
},
|
||||||
@@ -93,8 +95,10 @@ Object {
|
|||||||
"currency_code": "usd",
|
"currency_code": "usd",
|
||||||
"deleted_at": null,
|
"deleted_at": null,
|
||||||
"id": "test-price",
|
"id": "test-price",
|
||||||
|
"max_quantity": null,
|
||||||
|
"min_quantity": null,
|
||||||
|
"price_list_id": null,
|
||||||
"region_id": null,
|
"region_id": null,
|
||||||
"sale_amount": null,
|
|
||||||
"updated_at": Any<String>,
|
"updated_at": Any<String>,
|
||||||
"variant_id": "test-variant",
|
"variant_id": "test-variant",
|
||||||
},
|
},
|
||||||
@@ -150,8 +154,10 @@ Object {
|
|||||||
"currency_code": "usd",
|
"currency_code": "usd",
|
||||||
"deleted_at": null,
|
"deleted_at": null,
|
||||||
"id": Any<String>,
|
"id": Any<String>,
|
||||||
|
"max_quantity": null,
|
||||||
|
"min_quantity": null,
|
||||||
|
"price_list_id": null,
|
||||||
"region_id": null,
|
"region_id": null,
|
||||||
"sale_amount": null,
|
|
||||||
"updated_at": Any<String>,
|
"updated_at": Any<String>,
|
||||||
"variant_id": Any<String>,
|
"variant_id": Any<String>,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -162,8 +162,10 @@ Object {
|
|||||||
"currency_code": "usd",
|
"currency_code": "usd",
|
||||||
"deleted_at": null,
|
"deleted_at": null,
|
||||||
"id": "test-price",
|
"id": "test-price",
|
||||||
|
"max_quantity": null,
|
||||||
|
"min_quantity": null,
|
||||||
|
"price_list_id": null,
|
||||||
"region_id": null,
|
"region_id": null,
|
||||||
"sale_amount": null,
|
|
||||||
"updated_at": Any<String>,
|
"updated_at": Any<String>,
|
||||||
"variant_id": "test-variant",
|
"variant_id": "test-variant",
|
||||||
},
|
},
|
||||||
@@ -211,8 +213,10 @@ Object {
|
|||||||
"currency_code": "usd",
|
"currency_code": "usd",
|
||||||
"deleted_at": null,
|
"deleted_at": null,
|
||||||
"id": "test-price2",
|
"id": "test-price2",
|
||||||
|
"max_quantity": null,
|
||||||
|
"min_quantity": null,
|
||||||
|
"price_list_id": null,
|
||||||
"region_id": null,
|
"region_id": null,
|
||||||
"sale_amount": null,
|
|
||||||
"updated_at": Any<String>,
|
"updated_at": Any<String>,
|
||||||
"variant_id": "test-variant_2",
|
"variant_id": "test-variant_2",
|
||||||
},
|
},
|
||||||
@@ -260,8 +264,10 @@ Object {
|
|||||||
"currency_code": "usd",
|
"currency_code": "usd",
|
||||||
"deleted_at": null,
|
"deleted_at": null,
|
||||||
"id": "test-price1",
|
"id": "test-price1",
|
||||||
|
"max_quantity": null,
|
||||||
|
"min_quantity": null,
|
||||||
|
"price_list_id": null,
|
||||||
"region_id": null,
|
"region_id": null,
|
||||||
"sale_amount": null,
|
|
||||||
"updated_at": Any<String>,
|
"updated_at": Any<String>,
|
||||||
"variant_id": "test-variant_1",
|
"variant_id": "test-variant_1",
|
||||||
},
|
},
|
||||||
@@ -314,8 +320,10 @@ Object {
|
|||||||
"currency_code": "usd",
|
"currency_code": "usd",
|
||||||
"deleted_at": null,
|
"deleted_at": null,
|
||||||
"id": Any<String>,
|
"id": Any<String>,
|
||||||
|
"max_quantity": null,
|
||||||
|
"min_quantity": null,
|
||||||
|
"price_list_id": null,
|
||||||
"region_id": null,
|
"region_id": null,
|
||||||
"sale_amount": null,
|
|
||||||
"updated_at": Any<String>,
|
"updated_at": Any<String>,
|
||||||
"variant_id": Any<String>,
|
"variant_id": Any<String>,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -99,7 +99,9 @@ describe("/store/variants", () => {
|
|||||||
deleted_at: null,
|
deleted_at: null,
|
||||||
id: "test-price",
|
id: "test-price",
|
||||||
region_id: null,
|
region_id: null,
|
||||||
sale_amount: null,
|
min_quantity: null,
|
||||||
|
max_quantity: null,
|
||||||
|
price_list_id: null,
|
||||||
variant_id: "test-variant",
|
variant_id: "test-variant",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -193,7 +195,9 @@ describe("/store/variants", () => {
|
|||||||
deleted_at: null,
|
deleted_at: null,
|
||||||
id: "test-price",
|
id: "test-price",
|
||||||
region_id: null,
|
region_id: null,
|
||||||
sale_amount: null,
|
min_quantity: null,
|
||||||
|
max_quantity: null,
|
||||||
|
price_list_id: null,
|
||||||
variant_id: "test-variant",
|
variant_id: "test-variant",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -353,7 +353,9 @@ describe("/store/products", () => {
|
|||||||
deleted_at: null,
|
deleted_at: null,
|
||||||
id: "test-price",
|
id: "test-price",
|
||||||
region_id: null,
|
region_id: null,
|
||||||
sale_amount: null,
|
min_quantity: null,
|
||||||
|
max_quantity: null,
|
||||||
|
price_list_id: null,
|
||||||
updated_at: expect.any(String),
|
updated_at: expect.any(String),
|
||||||
variant_id: "test-variant",
|
variant_id: "test-variant",
|
||||||
},
|
},
|
||||||
@@ -396,7 +398,9 @@ describe("/store/products", () => {
|
|||||||
deleted_at: null,
|
deleted_at: null,
|
||||||
id: "test-price2",
|
id: "test-price2",
|
||||||
region_id: null,
|
region_id: null,
|
||||||
sale_amount: null,
|
min_quantity: null,
|
||||||
|
max_quantity: null,
|
||||||
|
price_list_id: null,
|
||||||
variant_id: "test-variant_2",
|
variant_id: "test-variant_2",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -438,7 +442,9 @@ describe("/store/products", () => {
|
|||||||
deleted_at: null,
|
deleted_at: null,
|
||||||
id: "test-price1",
|
id: "test-price1",
|
||||||
region_id: null,
|
region_id: null,
|
||||||
sale_amount: null,
|
min_quantity: null,
|
||||||
|
max_quantity: null,
|
||||||
|
price_list_id: null,
|
||||||
updated_at: expect.any(String),
|
updated_at: expect.any(String),
|
||||||
variant_id: "test-variant_1",
|
variant_id: "test-variant_1",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -36,17 +36,17 @@ module.exports = async (connection, data = {}) => {
|
|||||||
has_account: true,
|
has_account: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
const customer5 = manager.create(Customer, {
|
const customer5 = await manager.create(Customer, {
|
||||||
id: "test-customer-5",
|
id: "test-customer-5",
|
||||||
email: "test5@email.com",
|
email: "test5@email.com",
|
||||||
})
|
})
|
||||||
|
|
||||||
const customer6 = manager.create(Customer, {
|
const customer6 = await manager.create(Customer, {
|
||||||
id: "test-customer-6",
|
id: "test-customer-6",
|
||||||
email: "test6@email.com",
|
email: "test6@email.com",
|
||||||
})
|
})
|
||||||
|
|
||||||
const customer7 = manager.create(Customer, {
|
const customer7 = await manager.create(Customer, {
|
||||||
id: "test-customer-7",
|
id: "test-customer-7",
|
||||||
email: "test7@email.com",
|
email: "test7@email.com",
|
||||||
})
|
})
|
||||||
@@ -78,13 +78,13 @@ module.exports = async (connection, data = {}) => {
|
|||||||
name: "test-group-4",
|
name: "test-group-4",
|
||||||
})
|
})
|
||||||
|
|
||||||
const c_group_5 = manager.create(CustomerGroup, {
|
const c_group_5 = await manager.create(CustomerGroup, {
|
||||||
id: "test-group-5",
|
id: "test-group-5",
|
||||||
name: "test-group-5",
|
name: "test-group-5",
|
||||||
})
|
})
|
||||||
await manager.save(c_group_5)
|
await manager.save(c_group_5)
|
||||||
|
|
||||||
const c_group_6 = manager.create(CustomerGroup, {
|
const c_group_6 = await manager.create(CustomerGroup, {
|
||||||
id: "test-group-6",
|
id: "test-group-6",
|
||||||
name: "test-group-6",
|
name: "test-group-6",
|
||||||
})
|
})
|
||||||
@@ -99,7 +99,7 @@ module.exports = async (connection, data = {}) => {
|
|||||||
customer7.groups = [c_group_5, c_group_6]
|
customer7.groups = [c_group_5, c_group_6]
|
||||||
await manager.save(customer7)
|
await manager.save(customer7)
|
||||||
|
|
||||||
const c_group_delete = manager.create(CustomerGroup, {
|
const c_group_delete = await manager.create(CustomerGroup, {
|
||||||
id: "test-group-delete",
|
id: "test-group-delete",
|
||||||
name: "test-group-delete",
|
name: "test-group-delete",
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
const { PriceList, MoneyAmount } = require("@medusajs/medusa")
|
||||||
|
|
||||||
|
module.exports = async (connection, data = {}) => {
|
||||||
|
const manager = connection.manager
|
||||||
|
|
||||||
|
const priceListNoCustomerGroups = await manager.create(PriceList, {
|
||||||
|
id: "pl_no_customer_groups",
|
||||||
|
name: "VIP winter sale",
|
||||||
|
description: "Winter sale for VIP customers. 25% off selected items.",
|
||||||
|
type: "sale",
|
||||||
|
status: "active",
|
||||||
|
starts_at: "2022-07-01T00:00:00.000Z",
|
||||||
|
ends_at: "2022-07-31T00:00:00.000Z",
|
||||||
|
})
|
||||||
|
|
||||||
|
await manager.save(priceListNoCustomerGroups)
|
||||||
|
|
||||||
|
const moneyAmount1 = await manager.create(MoneyAmount, {
|
||||||
|
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",
|
||||||
|
})
|
||||||
|
|
||||||
|
await manager.save(moneyAmount1)
|
||||||
|
|
||||||
|
const moneyAmount2 = await manager.create(MoneyAmount, {
|
||||||
|
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",
|
||||||
|
})
|
||||||
|
|
||||||
|
await manager.save(moneyAmount2)
|
||||||
|
|
||||||
|
const moneyAmount3 = await manager.create(MoneyAmount, {
|
||||||
|
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",
|
||||||
|
})
|
||||||
|
|
||||||
|
await manager.save(moneyAmount3)
|
||||||
|
}
|
||||||
@@ -8,16 +8,16 @@
|
|||||||
"build": "babel src -d dist --extensions \".ts,.js\""
|
"build": "babel src -d dist --extensions \".ts,.js\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@medusajs/medusa": "1.2.0-dev-1646213107704",
|
"@medusajs/medusa": "1.2.0-dev-1647336201011",
|
||||||
"faker": "^5.5.3",
|
"faker": "^5.5.3",
|
||||||
"medusa-interfaces": "1.2.0-dev-1646213107704",
|
"medusa-interfaces": "1.2.0-dev-1647336201011",
|
||||||
"typeorm": "^0.2.31"
|
"typeorm": "^0.2.31"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "^7.12.10",
|
"@babel/cli": "^7.12.10",
|
||||||
"@babel/core": "^7.12.10",
|
"@babel/core": "^7.12.10",
|
||||||
"@babel/node": "^7.12.10",
|
"@babel/node": "^7.12.10",
|
||||||
"babel-preset-medusa-package": "1.1.19-dev-1646213107704",
|
"babel-preset-medusa-package": "1.1.19-dev-1647336201011",
|
||||||
"jest": "^26.6.3"
|
"jest": "^26.6.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1256,10 +1256,10 @@
|
|||||||
"@types/yargs" "^15.0.0"
|
"@types/yargs" "^15.0.0"
|
||||||
chalk "^4.0.0"
|
chalk "^4.0.0"
|
||||||
|
|
||||||
"@medusajs/medusa-cli@1.2.0-dev-1646213107704":
|
"@medusajs/medusa-cli@1.2.0-dev-1647336201011":
|
||||||
version "1.2.0-dev-1646213107704"
|
version "1.2.0-dev-1647336201011"
|
||||||
resolved "http://localhost:4873/@medusajs%2fmedusa-cli/-/medusa-cli-1.2.0-dev-1646213107704.tgz#b84f9143450f3c03e732277b6c5bbeb0df761b2e"
|
resolved "http://localhost:4873/@medusajs%2fmedusa-cli/-/medusa-cli-1.2.0-dev-1647336201011.tgz#9842f8d2f53d5a7d2207ec23d7d4b18a33e65c54"
|
||||||
integrity sha512-U1BqPe167vxpzf2YfopVpNTq0qcBkX3jkWFCNduHEjkBbuOXchlB/MheKtxIsxjHV1hzmjTr9uw3owu93pDYCQ==
|
integrity sha512-wVqcWMh0+8ko/Db66GDrp68XpaVkvPrjrc4mn7KdfD9DnIgFCOoFakuYsQUxHDNak9EA96idZSF8/hsHALlOqg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/polyfill" "^7.8.7"
|
"@babel/polyfill" "^7.8.7"
|
||||||
"@babel/runtime" "^7.9.6"
|
"@babel/runtime" "^7.9.6"
|
||||||
@@ -1277,8 +1277,8 @@
|
|||||||
is-valid-path "^0.1.1"
|
is-valid-path "^0.1.1"
|
||||||
joi-objectid "^3.0.1"
|
joi-objectid "^3.0.1"
|
||||||
meant "^1.0.1"
|
meant "^1.0.1"
|
||||||
medusa-core-utils "1.1.31-dev-1646213107704"
|
medusa-core-utils "1.1.31-dev-1647336201011"
|
||||||
medusa-telemetry "0.0.11-dev-1646213107704"
|
medusa-telemetry "0.0.11-dev-1647336201011"
|
||||||
netrc-parser "^3.1.6"
|
netrc-parser "^3.1.6"
|
||||||
open "^8.0.6"
|
open "^8.0.6"
|
||||||
ora "^5.4.1"
|
ora "^5.4.1"
|
||||||
@@ -1292,13 +1292,13 @@
|
|||||||
winston "^3.3.3"
|
winston "^3.3.3"
|
||||||
yargs "^15.3.1"
|
yargs "^15.3.1"
|
||||||
|
|
||||||
"@medusajs/medusa@1.2.0-dev-1646213107704":
|
"@medusajs/medusa@1.2.0-dev-1647336201011":
|
||||||
version "1.2.0-dev-1646213107704"
|
version "1.2.0-dev-1647336201011"
|
||||||
resolved "http://localhost:4873/@medusajs%2fmedusa/-/medusa-1.2.0-dev-1646213107704.tgz#40ed0efe4f8e242c620eb1a984f0904465576920"
|
resolved "http://localhost:4873/@medusajs%2fmedusa/-/medusa-1.2.0-dev-1647336201011.tgz#76c0c3b93adf55b463ec091d2e144616dc1293bf"
|
||||||
integrity sha512-pZnoM/U6WF4IdKriX7lKaxlmt9nlK4WoQ4PtzLjvZemjNHKriLjsg8ugvuBOuuzZJe66O8XEAXcoPh2o/SmAeg==
|
integrity sha512-t0eWo60Y5KsyN4ucWDjPyuI+4oOHrwqlrzegg4oogGiAfvq2rLDQFwxrfvzKlhgYtJUA58dvxMpZqG5n1NOJLQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@hapi/joi" "^16.1.8"
|
"@hapi/joi" "^16.1.8"
|
||||||
"@medusajs/medusa-cli" "1.2.0-dev-1646213107704"
|
"@medusajs/medusa-cli" "1.2.0-dev-1647336201011"
|
||||||
"@types/lodash" "^4.14.168"
|
"@types/lodash" "^4.14.168"
|
||||||
awilix "^4.2.3"
|
awilix "^4.2.3"
|
||||||
body-parser "^1.19.0"
|
body-parser "^1.19.0"
|
||||||
@@ -1322,8 +1322,8 @@
|
|||||||
joi "^17.3.0"
|
joi "^17.3.0"
|
||||||
joi-objectid "^3.0.1"
|
joi-objectid "^3.0.1"
|
||||||
jsonwebtoken "^8.5.1"
|
jsonwebtoken "^8.5.1"
|
||||||
medusa-core-utils "1.1.31-dev-1646213107704"
|
medusa-core-utils "1.1.31-dev-1647336201011"
|
||||||
medusa-test-utils "1.1.37-dev-1646213107704"
|
medusa-test-utils "1.1.37-dev-1647336201011"
|
||||||
morgan "^1.9.1"
|
morgan "^1.9.1"
|
||||||
multer "^1.4.2"
|
multer "^1.4.2"
|
||||||
passport "^0.4.0"
|
passport "^0.4.0"
|
||||||
@@ -1947,10 +1947,10 @@ babel-preset-jest@^26.6.2:
|
|||||||
babel-plugin-jest-hoist "^26.6.2"
|
babel-plugin-jest-hoist "^26.6.2"
|
||||||
babel-preset-current-node-syntax "^1.0.0"
|
babel-preset-current-node-syntax "^1.0.0"
|
||||||
|
|
||||||
babel-preset-medusa-package@1.1.19-dev-1646213107704:
|
babel-preset-medusa-package@1.1.19-dev-1647336201011:
|
||||||
version "1.1.19-dev-1646213107704"
|
version "1.1.19-dev-1647336201011"
|
||||||
resolved "http://localhost:4873/babel-preset-medusa-package/-/babel-preset-medusa-package-1.1.19-dev-1646213107704.tgz#820201a6abfbdb95af623f1cae8981bb5ccddb8d"
|
resolved "http://localhost:4873/babel-preset-medusa-package/-/babel-preset-medusa-package-1.1.19-dev-1647336201011.tgz#52a3e7ad80f216cdcae3732c22d9edb3d2504a92"
|
||||||
integrity sha512-gTkFWvA/i+UmPSK8O0J4HBXE1tqQSD/D2p3ernvcvoaaXYhiLhNUrRRe9F8YXdkFT4tVXy2DKz/eQyUG1dsnnA==
|
integrity sha512-oYbWdvcXHvGc14p6N4vyGjPK0gs/zO4gVMahrDHPcMGfmGOajkVBMnuMfiEzOaDHaiBchIsWCv9LYLFLcv8JjA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/plugin-proposal-class-properties" "^7.12.1"
|
"@babel/plugin-proposal-class-properties" "^7.12.1"
|
||||||
"@babel/plugin-proposal-decorators" "^7.12.1"
|
"@babel/plugin-proposal-decorators" "^7.12.1"
|
||||||
@@ -5140,25 +5140,25 @@ media-typer@0.3.0:
|
|||||||
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
||||||
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
|
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
|
||||||
|
|
||||||
medusa-core-utils@1.1.31-dev-1646213107704:
|
medusa-core-utils@1.1.31-dev-1647336201011:
|
||||||
version "1.1.31-dev-1646213107704"
|
version "1.1.31-dev-1647336201011"
|
||||||
resolved "http://localhost:4873/medusa-core-utils/-/medusa-core-utils-1.1.31-dev-1646213107704.tgz#40ca7c1a4b290b1af9cf855cfc3320c23bbf3c53"
|
resolved "http://localhost:4873/medusa-core-utils/-/medusa-core-utils-1.1.31-dev-1647336201011.tgz#6b6d9c9708d8377d1bddc8ef9d26723b550312c9"
|
||||||
integrity sha512-P3D/jVSnx0YYEU8hTVqEow4Xht/BAHnHyc/HSbU5YT7PtCwEd+MY7+bvSAkhbVXNDCVsxyGTDA2nuofPS/c4RQ==
|
integrity sha512-/qYyS0vE5lqHhNZfhNM2iTE5Sh8qYockVyHn2dapolBsmYE9f7IjhOsB3bz2o0b1J33L3se6iG0wSCQeN3INXA==
|
||||||
dependencies:
|
dependencies:
|
||||||
joi "^17.3.0"
|
joi "^17.3.0"
|
||||||
joi-objectid "^3.0.1"
|
joi-objectid "^3.0.1"
|
||||||
|
|
||||||
medusa-interfaces@1.2.0-dev-1646213107704:
|
medusa-interfaces@1.2.0-dev-1647336201011:
|
||||||
version "1.2.0-dev-1646213107704"
|
version "1.2.0-dev-1647336201011"
|
||||||
resolved "http://localhost:4873/medusa-interfaces/-/medusa-interfaces-1.2.0-dev-1646213107704.tgz#1a5310484afb5c1d127ba5775e08e3c0502e1488"
|
resolved "http://localhost:4873/medusa-interfaces/-/medusa-interfaces-1.2.0-dev-1647336201011.tgz#f6a25ef007c8ffe2828b4e2f64f23bd60929f4f5"
|
||||||
integrity sha512-3+Ve8f5a+dvdcRafOzAuisXk/vwY9fBXgshof8xYFgtDso2INwMHTNIEgUQrjMd8yXt2+EO8l9wlBeAr02eQ0A==
|
integrity sha512-8PvQpLfwB0YGTQU0qh/miDhJaOczoyCFs1bvlMbe3cOsn7ua7UKeUj+TLQQwh6ykbezjtggUgafEH+fKOmLUWg==
|
||||||
dependencies:
|
dependencies:
|
||||||
medusa-core-utils "1.1.31-dev-1646213107704"
|
medusa-core-utils "1.1.31-dev-1647336201011"
|
||||||
|
|
||||||
medusa-telemetry@0.0.11-dev-1646213107704:
|
medusa-telemetry@0.0.11-dev-1647336201011:
|
||||||
version "0.0.11-dev-1646213107704"
|
version "0.0.11-dev-1647336201011"
|
||||||
resolved "http://localhost:4873/medusa-telemetry/-/medusa-telemetry-0.0.11-dev-1646213107704.tgz#7dc191fd263774a31795c2696ffe015a2b5f7443"
|
resolved "http://localhost:4873/medusa-telemetry/-/medusa-telemetry-0.0.11-dev-1647336201011.tgz#d8a33d114a5bd4e6d2bdef112a07eed96a5b2f8d"
|
||||||
integrity sha512-BLKdzaU1sE9Eqnt3lmE6wkocOuC7wMT0l2OWc0aW6YTRRWDOffxOuANku1bZCIdVtbJ1q25UpeY2dwuD7lXhZw==
|
integrity sha512-am/sacr0XhCyc2OT6w8QCLuI7AXioJvaXxPzB4P1n5OiEGOn3IJIHZJY22MQzdY/dJZ7Rb4XuFlXdZInbc4woA==
|
||||||
dependencies:
|
dependencies:
|
||||||
axios "^0.21.1"
|
axios "^0.21.1"
|
||||||
axios-retry "^3.1.9"
|
axios-retry "^3.1.9"
|
||||||
@@ -5170,13 +5170,13 @@ medusa-telemetry@0.0.11-dev-1646213107704:
|
|||||||
remove-trailing-slash "^0.1.1"
|
remove-trailing-slash "^0.1.1"
|
||||||
uuid "^8.3.2"
|
uuid "^8.3.2"
|
||||||
|
|
||||||
medusa-test-utils@1.1.37-dev-1646213107704:
|
medusa-test-utils@1.1.37-dev-1647336201011:
|
||||||
version "1.1.37-dev-1646213107704"
|
version "1.1.37-dev-1647336201011"
|
||||||
resolved "http://localhost:4873/medusa-test-utils/-/medusa-test-utils-1.1.37-dev-1646213107704.tgz#5fdb8298ac482f1d89475115d7727dd5227ac866"
|
resolved "http://localhost:4873/medusa-test-utils/-/medusa-test-utils-1.1.37-dev-1647336201011.tgz#6a1cb877abf79210db0065095b1baa0427eb93d1"
|
||||||
integrity sha512-S42x4NaioWXPwQluO+iOYDwV0LUmr7q58XClFJA8j7Nw07IFKJU2PPHAAPrbpWQKpa2pdCBYdR0FUID4/VOfXw==
|
integrity sha512-K6J7FlUm3q3jL94Ye01gpk3rcan9JEH1SDRoUm1zqZ0XAX3tl49R7VQtHO7kWcO05o2JUJt8/+LvNv6suth4ow==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/plugin-transform-classes" "^7.9.5"
|
"@babel/plugin-transform-classes" "^7.9.5"
|
||||||
medusa-core-utils "1.1.31-dev-1646213107704"
|
medusa-core-utils "1.1.31-dev-1647336201011"
|
||||||
randomatic "^3.1.1"
|
randomatic "^3.1.1"
|
||||||
|
|
||||||
merge-descriptors@1.0.1:
|
merge-descriptors@1.0.1:
|
||||||
|
|||||||
@@ -1,33 +1,33 @@
|
|||||||
import { Router } from "express"
|
|
||||||
import cors from "cors"
|
import cors from "cors"
|
||||||
|
import { Router } from "express"
|
||||||
import middlewares from "../../middlewares"
|
import middlewares from "../../middlewares"
|
||||||
|
import appRoutes from "./apps"
|
||||||
import authRoutes from "./auth"
|
import authRoutes from "./auth"
|
||||||
import productRoutes from "./products"
|
import collectionRoutes from "./collections"
|
||||||
import userRoutes, { unauthenticatedUserRoutes } from "./users"
|
import customerGroupRoutes from "./customer-groups"
|
||||||
|
import customerRoutes from "./customers"
|
||||||
|
import discountRoutes from "./discounts"
|
||||||
|
import draftOrderRoutes from "./draft-orders"
|
||||||
|
import giftCardRoutes from "./gift-cards"
|
||||||
import inviteRoutes, { unauthenticatedInviteRoutes } from "./invites"
|
import inviteRoutes, { unauthenticatedInviteRoutes } from "./invites"
|
||||||
|
import noteRoutes from "./notes"
|
||||||
|
import notificationRoutes from "./notifications"
|
||||||
|
import orderRoutes from "./orders"
|
||||||
|
import priceListRoutes from "./price-lists"
|
||||||
|
import productTagRoutes from "./product-tags"
|
||||||
|
import productTypesRoutes from "./product-types"
|
||||||
|
import productRoutes from "./products"
|
||||||
import regionRoutes from "./regions"
|
import regionRoutes from "./regions"
|
||||||
|
import returnReasonRoutes from "./return-reasons"
|
||||||
|
import returnRoutes from "./returns"
|
||||||
import shippingOptionRoutes from "./shipping-options"
|
import shippingOptionRoutes from "./shipping-options"
|
||||||
import shippingProfileRoutes from "./shipping-profiles"
|
import shippingProfileRoutes from "./shipping-profiles"
|
||||||
import discountRoutes from "./discounts"
|
|
||||||
import giftCardRoutes from "./gift-cards"
|
|
||||||
import orderRoutes from "./orders"
|
|
||||||
import returnReasonRoutes from "./return-reasons"
|
|
||||||
import storeRoutes from "./store"
|
import storeRoutes from "./store"
|
||||||
import uploadRoutes from "./uploads"
|
|
||||||
import customerRoutes from "./customers"
|
|
||||||
import appRoutes from "./apps"
|
|
||||||
import swapRoutes from "./swaps"
|
import swapRoutes from "./swaps"
|
||||||
import returnRoutes from "./returns"
|
|
||||||
import variantRoutes from "./variants"
|
|
||||||
import draftOrderRoutes from "./draft-orders"
|
|
||||||
import collectionRoutes from "./collections"
|
|
||||||
import productTagRoutes from "./product-tags"
|
|
||||||
import notificationRoutes from "./notifications"
|
|
||||||
import noteRoutes from "./notes"
|
|
||||||
import taxRateRoutes from "./tax-rates"
|
import taxRateRoutes from "./tax-rates"
|
||||||
import productTypesRoutes from "./product-types"
|
import uploadRoutes from "./uploads"
|
||||||
import customerGroupRoutes from "./customer-groups"
|
import userRoutes, { unauthenticatedUserRoutes } from "./users"
|
||||||
|
import variantRoutes from "./variants"
|
||||||
|
|
||||||
const route = Router()
|
const route = Router()
|
||||||
|
|
||||||
@@ -86,6 +86,7 @@ export default (app, container, config) => {
|
|||||||
inviteRoutes(route)
|
inviteRoutes(route)
|
||||||
taxRateRoutes(route)
|
taxRateRoutes(route)
|
||||||
customerGroupRoutes(route)
|
customerGroupRoutes(route)
|
||||||
|
priceListRoutes(route)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { PriceListServiceMock } from "../../../../../services/__mocks__/price-list"
|
||||||
|
|
||||||
|
describe("POST /price-lists/:id/prices/batch", () => {
|
||||||
|
describe("successfully adds several new prices to a price list", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request(
|
||||||
|
"POST",
|
||||||
|
`/admin/price-lists/pl_1234/prices/batch`,
|
||||||
|
{
|
||||||
|
payload: {
|
||||||
|
prices: [
|
||||||
|
{
|
||||||
|
currency_code: "usd",
|
||||||
|
amount: 500,
|
||||||
|
min_quantity: 10,
|
||||||
|
max_quantity: 20,
|
||||||
|
variant_id: "variant_12",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currency_code: "usd",
|
||||||
|
amount: 430,
|
||||||
|
min_quantity: 21,
|
||||||
|
max_quantity: 40,
|
||||||
|
variant_id: "variant_12",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
adminSession: {
|
||||||
|
jwt: {
|
||||||
|
userId: IdMap.getId("admin_user"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 200", () => {
|
||||||
|
expect(subject.status).toEqual(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls PriceListService addPrices", () => {
|
||||||
|
expect(PriceListServiceMock.addPrices).toHaveBeenCalledTimes(1)
|
||||||
|
expect(PriceListServiceMock.addPrices).toHaveBeenCalledWith(
|
||||||
|
"pl_1234",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
currency_code: "usd",
|
||||||
|
amount: 500,
|
||||||
|
min_quantity: 10,
|
||||||
|
max_quantity: 20,
|
||||||
|
variant_id: "variant_12",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currency_code: "usd",
|
||||||
|
amount: 430,
|
||||||
|
min_quantity: 21,
|
||||||
|
max_quantity: 40,
|
||||||
|
variant_id: "variant_12",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
undefined
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("fails if no prices were provided", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request(
|
||||||
|
"POST",
|
||||||
|
`/admin/price-lists/pl_1234/prices/batch`,
|
||||||
|
{
|
||||||
|
payload: {},
|
||||||
|
adminSession: {
|
||||||
|
jwt: {
|
||||||
|
userId: IdMap.getId("admin_user"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 400", () => {
|
||||||
|
expect(subject.status).toEqual(400)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns descriptive error that name is missing", () => {
|
||||||
|
expect(subject.body.type).toEqual("invalid_data")
|
||||||
|
expect(subject.body.message).toEqual("prices must be an array")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { PriceListServiceMock } from "../../../../../services/__mocks__/price-list"
|
||||||
|
|
||||||
|
describe("POST /price-lists", () => {
|
||||||
|
describe("successfully creates a price list", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request("POST", `/admin/price-lists`, {
|
||||||
|
payload: {
|
||||||
|
name: "My Price List",
|
||||||
|
description: "testing",
|
||||||
|
ends_at: "2022-03-14T08:28:38.551Z",
|
||||||
|
starts_at: "2022-03-14T08:28:38.551Z",
|
||||||
|
customer_groups: [
|
||||||
|
{
|
||||||
|
id: "gc_123",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: "sale",
|
||||||
|
prices: [
|
||||||
|
{
|
||||||
|
currency_code: "usd",
|
||||||
|
amount: 500,
|
||||||
|
min_quantity: 10,
|
||||||
|
max_quantity: 20,
|
||||||
|
variant_id: "variant_12",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currency_code: "usd",
|
||||||
|
amount: 430,
|
||||||
|
min_quantity: 21,
|
||||||
|
max_quantity: 40,
|
||||||
|
variant_id: "variant_12",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
adminSession: {
|
||||||
|
jwt: {
|
||||||
|
userId: IdMap.getId("admin_user"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 200", () => {
|
||||||
|
expect(subject.status).toEqual(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls PriceListService addPrices", () => {
|
||||||
|
expect(PriceListServiceMock.create).toHaveBeenCalledTimes(1)
|
||||||
|
expect(PriceListServiceMock.create).toHaveBeenCalledWith({
|
||||||
|
name: "My Price List",
|
||||||
|
description: "testing",
|
||||||
|
ends_at: "2022-03-14T08:28:38.551Z",
|
||||||
|
starts_at: "2022-03-14T08:28:38.551Z",
|
||||||
|
customer_groups: [
|
||||||
|
{
|
||||||
|
id: "gc_123",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: "sale",
|
||||||
|
prices: [
|
||||||
|
{
|
||||||
|
currency_code: "usd",
|
||||||
|
amount: 500,
|
||||||
|
min_quantity: 10,
|
||||||
|
max_quantity: 20,
|
||||||
|
variant_id: "variant_12",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currency_code: "usd",
|
||||||
|
amount: 430,
|
||||||
|
min_quantity: 21,
|
||||||
|
max_quantity: 40,
|
||||||
|
variant_id: "variant_12",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("fails if required fields are missing", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request("POST", `/admin/price-lists`, {
|
||||||
|
payload: {
|
||||||
|
description: "bad payload",
|
||||||
|
},
|
||||||
|
adminSession: {
|
||||||
|
jwt: {
|
||||||
|
userId: IdMap.getId("admin_user"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 400", () => {
|
||||||
|
expect(subject.status).toEqual(400)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns descriptive error that several fields are missing", () => {
|
||||||
|
expect(subject.body.type).toEqual("invalid_data")
|
||||||
|
expect(subject.body.message).toEqual(
|
||||||
|
"name must be a string, type must be a valid enum value, prices must be an array"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { PriceListServiceMock } from "../../../../../services/__mocks__/price-list"
|
||||||
|
|
||||||
|
describe("POST /price-lists/:id/prices/batch", () => {
|
||||||
|
describe("successfully adds several new prices to a price list", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request("DELETE", `/admin/price-lists/pl_1234`, {
|
||||||
|
adminSession: {
|
||||||
|
jwt: {
|
||||||
|
userId: IdMap.getId("admin_user"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 200", () => {
|
||||||
|
expect(subject.status).toEqual(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls PriceListService addPrices", () => {
|
||||||
|
expect(PriceListServiceMock.delete).toHaveBeenCalledTimes(1)
|
||||||
|
expect(PriceListServiceMock.delete).toHaveBeenCalledWith("pl_1234")
|
||||||
|
|
||||||
|
expect(subject.body).toEqual({
|
||||||
|
id: "pl_1234",
|
||||||
|
object: "price-list",
|
||||||
|
deleted: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { PriceListServiceMock } from "../../../../../services/__mocks__/price-list"
|
||||||
|
|
||||||
|
describe("DELETE /price-lists/:id/prices/batch", () => {
|
||||||
|
describe("successfully adds several new prices to a price list", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request(
|
||||||
|
"DELETE",
|
||||||
|
`/admin/price-lists/pl_1234/prices/batch`,
|
||||||
|
{
|
||||||
|
payload: {
|
||||||
|
price_ids: ["price_1234", "price_1235", "price_1236", "price_1237"],
|
||||||
|
},
|
||||||
|
adminSession: {
|
||||||
|
jwt: {
|
||||||
|
userId: IdMap.getId("admin_user"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 200", () => {
|
||||||
|
expect(subject.status).toEqual(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls PriceListService addPrices", () => {
|
||||||
|
expect(PriceListServiceMock.deletePrices).toHaveBeenCalledTimes(1)
|
||||||
|
expect(PriceListServiceMock.deletePrices).toHaveBeenCalledWith(
|
||||||
|
"pl_1234",
|
||||||
|
["price_1234", "price_1235", "price_1236", "price_1237"]
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("fails if no prices were provided", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request(
|
||||||
|
"DELETE",
|
||||||
|
`/admin/price-lists/pl_1234/prices/batch`,
|
||||||
|
{
|
||||||
|
payload: {},
|
||||||
|
adminSession: {
|
||||||
|
jwt: {
|
||||||
|
userId: IdMap.getId("admin_user"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 400", () => {
|
||||||
|
expect(subject.status).toEqual(400)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns descriptive error that price_ids is missing", () => {
|
||||||
|
expect(subject.body.type).toEqual("invalid_data")
|
||||||
|
expect(subject.body.message).toEqual(
|
||||||
|
"each value in price_ids must be a string, price_ids should not be empty"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { defaultAdminPriceListFields, defaultAdminPriceListRelations } from ".."
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { PriceListServiceMock } from "../../../../../services/__mocks__/price-list"
|
||||||
|
|
||||||
|
describe("GET /price-lists/:id", () => {
|
||||||
|
describe("", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request("GET", `/admin/price-lists/pl_1234`, {
|
||||||
|
adminSession: {
|
||||||
|
jwt: {
|
||||||
|
userId: IdMap.getId("admin_user"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 200", () => {
|
||||||
|
expect(subject.status).toEqual(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls PriceListService retrieve", () => {
|
||||||
|
expect(PriceListServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
||||||
|
expect(PriceListServiceMock.retrieve).toHaveBeenCalledWith("pl_1234", {
|
||||||
|
relations: defaultAdminPriceListRelations,
|
||||||
|
select: defaultAdminPriceListFields,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { PriceListServiceMock } from "../../../../../services/__mocks__/price-list"
|
||||||
|
|
||||||
|
describe("GET /price-lists", () => {
|
||||||
|
describe("successfully lists price lists", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request("GET", `/admin/price-lists`, {
|
||||||
|
adminSession: {
|
||||||
|
jwt: {
|
||||||
|
userId: IdMap.getId("admin_user"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 200", () => {
|
||||||
|
expect(subject.status).toEqual(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls PriceListService listAndCount", () => {
|
||||||
|
expect(PriceListServiceMock.listAndCount).toHaveBeenCalledTimes(1)
|
||||||
|
expect(PriceListServiceMock.listAndCount).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
created_at: undefined,
|
||||||
|
deleted_at: undefined,
|
||||||
|
description: undefined,
|
||||||
|
id: undefined,
|
||||||
|
name: undefined,
|
||||||
|
q: undefined,
|
||||||
|
status: undefined,
|
||||||
|
type: undefined,
|
||||||
|
updated_at: undefined,
|
||||||
|
},
|
||||||
|
{ order: { created_at: "DESC" }, relations: [], skip: 0, take: 10 }
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { PriceListServiceMock } from "../../../../../services/__mocks__/price-list"
|
||||||
|
|
||||||
|
describe("POST /price-lists/:id", () => {
|
||||||
|
describe("successfully updates a price list", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request("POST", `/admin/price-lists/pl_1234`, {
|
||||||
|
payload: {
|
||||||
|
description: "new description",
|
||||||
|
},
|
||||||
|
adminSession: {
|
||||||
|
jwt: {
|
||||||
|
userId: IdMap.getId("admin_user"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 200", () => {
|
||||||
|
expect(subject.status).toEqual(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls PriceListService update", () => {
|
||||||
|
expect(PriceListServiceMock.update).toHaveBeenCalledTimes(1)
|
||||||
|
expect(PriceListServiceMock.update).toHaveBeenCalledWith("pl_1234", {
|
||||||
|
description: "new description",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import { Type } from "class-transformer"
|
||||||
|
import { IsArray, IsBoolean, IsOptional, ValidateNested } from "class-validator"
|
||||||
|
import { defaultAdminPriceListFields, defaultAdminPriceListRelations } from "."
|
||||||
|
import { PriceList } from "../../../.."
|
||||||
|
import PriceListService from "../../../../services/price-list"
|
||||||
|
import { AdminPriceListPricesUpdateReq } from "../../../../types/price-list"
|
||||||
|
import { validator } from "../../../../utils/validator"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @oas [post] /price-lists/{id}/prices/batch
|
||||||
|
* operationId: "PostPriceListsPriceListPricesBatch"
|
||||||
|
* summary: "Batch update prices for a Price List"
|
||||||
|
* description: "Batch update prices for a Price List"
|
||||||
|
* x-authenticated: true
|
||||||
|
* parameters:
|
||||||
|
* - (path) id=* {string} The id of the Price List to update prices for.
|
||||||
|
* requestBody:
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* properties:
|
||||||
|
* prices:
|
||||||
|
* description: The prices to update or add.
|
||||||
|
* type: array
|
||||||
|
* items:
|
||||||
|
* properties:
|
||||||
|
* id:
|
||||||
|
* description: The id of the price.
|
||||||
|
* type: string
|
||||||
|
* status:
|
||||||
|
* description: The status of the Price List.
|
||||||
|
* type: string
|
||||||
|
* enum:
|
||||||
|
* - active
|
||||||
|
* - draft
|
||||||
|
* region_id:
|
||||||
|
* description: The id of the Region for which the price is used.
|
||||||
|
* type: string
|
||||||
|
* currency_code:
|
||||||
|
* description: The 3 character ISO currency code for which the price will be used.
|
||||||
|
* type: string
|
||||||
|
* amount:
|
||||||
|
* description: The amount of the price.
|
||||||
|
* type: number
|
||||||
|
* min_quantity:
|
||||||
|
* description: The minimum quantity for which the price will be used.
|
||||||
|
* type: number
|
||||||
|
* max_quantity:
|
||||||
|
* description: The maximum quantity for which the price will be used.
|
||||||
|
* type: number
|
||||||
|
* override:
|
||||||
|
* description: "If true the prices will replace all existing prices associated with the Price List."
|
||||||
|
* type: boolean
|
||||||
|
* tags:
|
||||||
|
* - Price List
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: OK
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* properties:
|
||||||
|
* id:
|
||||||
|
* type: string
|
||||||
|
* description: The id of the deleted Price List.
|
||||||
|
* object:
|
||||||
|
* type: string
|
||||||
|
* description: The type of the object that was deleted.
|
||||||
|
* deleted:
|
||||||
|
* type: boolean
|
||||||
|
*/
|
||||||
|
export default async (req, res) => {
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
const validated = await validator(AdminPostPriceListPricesPricesReq, req.body)
|
||||||
|
|
||||||
|
const priceListService: PriceListService =
|
||||||
|
req.scope.resolve("priceListService")
|
||||||
|
|
||||||
|
await priceListService.addPrices(id, validated.prices, validated.override)
|
||||||
|
|
||||||
|
const priceList = await priceListService.retrieve(id, {
|
||||||
|
select: defaultAdminPriceListFields as (keyof PriceList)[],
|
||||||
|
relations: defaultAdminPriceListRelations,
|
||||||
|
})
|
||||||
|
|
||||||
|
res.json({ price_list: priceList })
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AdminPostPriceListPricesPricesReq {
|
||||||
|
@IsArray()
|
||||||
|
@Type(() => AdminPriceListPricesUpdateReq)
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
prices: AdminPriceListPricesUpdateReq[]
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
override?: boolean
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
import { Type } from "class-transformer"
|
||||||
|
import {
|
||||||
|
IsArray,
|
||||||
|
IsEnum,
|
||||||
|
IsOptional,
|
||||||
|
IsString,
|
||||||
|
ValidateNested,
|
||||||
|
} from "class-validator"
|
||||||
|
import PriceListService from "../../../../services/price-list"
|
||||||
|
import {
|
||||||
|
AdminPriceListPricesCreateReq,
|
||||||
|
PriceListStatus,
|
||||||
|
PriceListType,
|
||||||
|
} from "../../../../types/price-list"
|
||||||
|
import { validator } from "../../../../utils/validator"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @oas [post] /price_lists
|
||||||
|
* operationId: "PostPriceListsPriceList"
|
||||||
|
* summary: "Creates a Price List"
|
||||||
|
* description: "Creates a Price List"
|
||||||
|
* x-authenticated: true
|
||||||
|
* requestBody:
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* properties:
|
||||||
|
* name:
|
||||||
|
* description: "The name of the Price List"
|
||||||
|
* type: string
|
||||||
|
* description:
|
||||||
|
* description: "A description of the Price List."
|
||||||
|
* type: string
|
||||||
|
* type:
|
||||||
|
* description: The type of the Price List.
|
||||||
|
* type: string
|
||||||
|
* enum:
|
||||||
|
* - sale
|
||||||
|
* - override
|
||||||
|
* status:
|
||||||
|
* description: The status of the Price List.
|
||||||
|
* type: string
|
||||||
|
* enum:
|
||||||
|
* - active
|
||||||
|
* - draft
|
||||||
|
* prices:
|
||||||
|
* description: The prices of the Price List.
|
||||||
|
* type: array
|
||||||
|
* items:
|
||||||
|
* properties:
|
||||||
|
* region_id:
|
||||||
|
* description: The id of the Region for which the price is used.
|
||||||
|
* type: string
|
||||||
|
* currency_code:
|
||||||
|
* description: The 3 character ISO currency code for which the price will be used.
|
||||||
|
* type: string
|
||||||
|
* amount:
|
||||||
|
* description: The amount to charge for the Product Variant.
|
||||||
|
* type: integer
|
||||||
|
* min_quantity:
|
||||||
|
* description: The minimum quantity for which the price will be used.
|
||||||
|
* type: integer
|
||||||
|
* max_quantity:
|
||||||
|
* description: The maximum quantity for which the price will be used.
|
||||||
|
* type: integer
|
||||||
|
* customer_groups:
|
||||||
|
* type: array
|
||||||
|
* description: A list of customer groups that the Price List applies to.
|
||||||
|
* items:
|
||||||
|
* required:
|
||||||
|
* - id
|
||||||
|
* properties:
|
||||||
|
* id:
|
||||||
|
* description: The id of a customer group
|
||||||
|
* type: string
|
||||||
|
* tags:
|
||||||
|
* - Price List
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: OK
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* properties:
|
||||||
|
* product:
|
||||||
|
* $ref: "#/components/schemas/price_list"
|
||||||
|
*/
|
||||||
|
export default async (req, res) => {
|
||||||
|
const validated = await validator(AdminPostPriceListsPriceListReq, req.body)
|
||||||
|
|
||||||
|
const priceListService: PriceListService =
|
||||||
|
req.scope.resolve("priceListService")
|
||||||
|
|
||||||
|
const priceList = await priceListService.create(validated)
|
||||||
|
|
||||||
|
res.json({ price_list: priceList })
|
||||||
|
}
|
||||||
|
|
||||||
|
class CustomerGroup {
|
||||||
|
@IsString()
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AdminPostPriceListsPriceListReq {
|
||||||
|
@IsString()
|
||||||
|
name: string
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
description: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
starts_at?: Date
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
ends_at?: Date
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsEnum(PriceListStatus)
|
||||||
|
status?: PriceListStatus
|
||||||
|
|
||||||
|
@IsEnum(PriceListType)
|
||||||
|
type: PriceListType
|
||||||
|
|
||||||
|
@IsArray()
|
||||||
|
@Type(() => AdminPriceListPricesCreateReq)
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
prices: AdminPriceListPricesCreateReq[]
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsArray()
|
||||||
|
@Type(() => CustomerGroup)
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
customer_groups?: CustomerGroup[]
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import PriceListService from "../../../../services/price-list"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @oas [delete] /price-lists/{id}
|
||||||
|
* operationId: "DeletePriceListsPriceList"
|
||||||
|
* summary: "Delete a Price List"
|
||||||
|
* description: "Deletes a Price List"
|
||||||
|
* x-authenticated: true
|
||||||
|
* parameters:
|
||||||
|
* - (path) id=* {string} The id of the Price List to delete.
|
||||||
|
* tags:
|
||||||
|
* - Price List
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: OK
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* properties:
|
||||||
|
* id:
|
||||||
|
* type: string
|
||||||
|
* description: The id of the deleted Price List.
|
||||||
|
* object:
|
||||||
|
* type: string
|
||||||
|
* description: The type of the object that was deleted.
|
||||||
|
* deleted:
|
||||||
|
* type: boolean
|
||||||
|
*/
|
||||||
|
export default async (req, res) => {
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
const priceListService: PriceListService =
|
||||||
|
req.scope.resolve("priceListService")
|
||||||
|
await priceListService.delete(id)
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
id,
|
||||||
|
object: "price-list",
|
||||||
|
deleted: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import { ArrayNotEmpty, IsString } from "class-validator"
|
||||||
|
import PriceListService from "../../../../services/price-list"
|
||||||
|
import { validator } from "../../../../utils/validator"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @oas [delete] /price-lists/{id}/prices/batch
|
||||||
|
* operationId: "DeletePriceListsPriceListPricesBatch"
|
||||||
|
* summary: "Batch delete prices that belongs to a Price List"
|
||||||
|
* description: "Batch delete prices that belongs to a Price List"
|
||||||
|
* x-authenticated: true
|
||||||
|
* parameters:
|
||||||
|
* - (path) id=* {string} The id of the Price List that the Money Amounts that will be deleted belongs to.
|
||||||
|
* requestBody:
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* properties:
|
||||||
|
* price_ids:
|
||||||
|
* description: The price id's of the Money Amounts to delete.
|
||||||
|
* type: array
|
||||||
|
* items:
|
||||||
|
* type: string
|
||||||
|
* tags:
|
||||||
|
* - Price List
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: OK
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* properties:
|
||||||
|
* ids:
|
||||||
|
* type: array
|
||||||
|
* items:
|
||||||
|
* type: string
|
||||||
|
* description: The id of the deleted Money Amount.
|
||||||
|
* object:
|
||||||
|
* type: string
|
||||||
|
* description: The type of the object that was deleted.
|
||||||
|
* deleted:
|
||||||
|
* type: boolean
|
||||||
|
*/
|
||||||
|
export default async (req, res) => {
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
const validated = await validator(
|
||||||
|
AdminDeletePriceListPricesPricesReq,
|
||||||
|
req.body
|
||||||
|
)
|
||||||
|
|
||||||
|
const priceListService: PriceListService =
|
||||||
|
req.scope.resolve("priceListService")
|
||||||
|
|
||||||
|
await priceListService.deletePrices(id, validated.price_ids)
|
||||||
|
|
||||||
|
res.json({ ids: validated.price_ids, object: "money-amount", deleted: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AdminDeletePriceListPricesPricesReq {
|
||||||
|
@ArrayNotEmpty()
|
||||||
|
@IsString({ each: true })
|
||||||
|
price_ids: string[]
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { defaultAdminPriceListFields, defaultAdminPriceListRelations } from "."
|
||||||
|
import { PriceList } from "../../../.."
|
||||||
|
import PriceListService from "../../../../services/price-list"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @oas [get] /price-lists/{id}
|
||||||
|
* operationId: "GetPriceListsPriceList"
|
||||||
|
* summary: "Retrieve a Price List"
|
||||||
|
* description: "Retrieves a Price List."
|
||||||
|
* x-authenticated: true
|
||||||
|
* parameters:
|
||||||
|
* - (path) id=* {string} The id of the Price List.
|
||||||
|
* tags:
|
||||||
|
* - Price List
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: OK
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* properties:
|
||||||
|
* price_list:
|
||||||
|
* $ref: "#/components/schemas/price_list"
|
||||||
|
*/
|
||||||
|
export default async (req, res) => {
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
const priceListService: PriceListService =
|
||||||
|
req.scope.resolve("priceListService")
|
||||||
|
|
||||||
|
const priceList = await priceListService.retrieve(id, {
|
||||||
|
select: defaultAdminPriceListFields as (keyof PriceList)[],
|
||||||
|
relations: defaultAdminPriceListRelations,
|
||||||
|
})
|
||||||
|
|
||||||
|
res.status(200).json({ price_list: priceList })
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { Router } from "express"
|
||||||
|
import "reflect-metadata"
|
||||||
|
import { PriceList } from "../../../.."
|
||||||
|
import { DeleteResponse, PaginatedResponse } from "../../../../types/common"
|
||||||
|
import middlewares from "../../../middlewares"
|
||||||
|
|
||||||
|
const route = Router()
|
||||||
|
|
||||||
|
export default (app) => {
|
||||||
|
app.use("/price-lists", route)
|
||||||
|
|
||||||
|
route.get("/:id", middlewares.wrap(require("./get-price-list").default))
|
||||||
|
|
||||||
|
route.get("/", middlewares.wrap(require("./list-price-lists").default))
|
||||||
|
|
||||||
|
route.post("/", middlewares.wrap(require("./create-price-list").default))
|
||||||
|
|
||||||
|
route.post("/:id", middlewares.wrap(require("./update-price-list").default))
|
||||||
|
|
||||||
|
route.delete("/:id", middlewares.wrap(require("./delete-price-list").default))
|
||||||
|
|
||||||
|
route.delete(
|
||||||
|
"/:id/prices/batch",
|
||||||
|
middlewares.wrap(require("./delete-prices-batch").default)
|
||||||
|
)
|
||||||
|
|
||||||
|
route.post(
|
||||||
|
"/:id/prices/batch",
|
||||||
|
middlewares.wrap(require("./add-prices-batch").default)
|
||||||
|
)
|
||||||
|
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultAdminPriceListFields = [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
"type",
|
||||||
|
"status",
|
||||||
|
"starts_at",
|
||||||
|
"ends_at",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"deleted_at",
|
||||||
|
]
|
||||||
|
|
||||||
|
export const defaultAdminPriceListRelations = ["prices", "customer_groups"]
|
||||||
|
|
||||||
|
export const allowedAdminPriceListFields = ["prices", "customer_groups"]
|
||||||
|
|
||||||
|
export type AdminPriceListRes = {
|
||||||
|
price_list: PriceList
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AdminPriceListDeleteRes = DeleteResponse
|
||||||
|
|
||||||
|
export type AdminPriceListsListRes = PaginatedResponse & {
|
||||||
|
price_lists: PriceList[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export * from "./add-prices-batch"
|
||||||
|
export * from "./create-price-list"
|
||||||
|
export * from "./delete-price-list"
|
||||||
|
export * from "./get-price-list"
|
||||||
|
export * from "./list-price-lists"
|
||||||
|
export * from "./update-price-list"
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
import { Type } from "class-transformer"
|
||||||
|
import { IsNumber, IsOptional, IsString } from "class-validator"
|
||||||
|
import omit from "lodash/omit"
|
||||||
|
import { PriceList } from "../../../.."
|
||||||
|
import PriceListService from "../../../../services/price-list"
|
||||||
|
import { FindConfig } from "../../../../types/common"
|
||||||
|
import { FilterablePriceListProps } from "../../../../types/price-list"
|
||||||
|
import { validator } from "../../../../utils/validator"
|
||||||
|
/**
|
||||||
|
* @oas [get] /price-lists
|
||||||
|
* operationId: "GetPriceLists"
|
||||||
|
* summary: "List Price Lists"
|
||||||
|
* description: "Retrieves a list of Price Lists."
|
||||||
|
* x-authenticated: true
|
||||||
|
* tags:
|
||||||
|
* - Price List
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: OK
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* properties:
|
||||||
|
* price_lists:
|
||||||
|
* type: array
|
||||||
|
* items:
|
||||||
|
* $ref: "#/components/schemas/price_list"
|
||||||
|
* count:
|
||||||
|
* description: The number of Price Lists.
|
||||||
|
* type: integer
|
||||||
|
* offset:
|
||||||
|
* description: The offset of the Price List query.
|
||||||
|
* type: integer
|
||||||
|
* limit:
|
||||||
|
* description: The limit of the Price List query.
|
||||||
|
* type: integer
|
||||||
|
*/
|
||||||
|
export default async (req, res) => {
|
||||||
|
const validated = await validator(
|
||||||
|
AdminGetPriceListPaginationParams,
|
||||||
|
req.query
|
||||||
|
)
|
||||||
|
|
||||||
|
const priceListService: PriceListService =
|
||||||
|
req.scope.resolve("priceListService")
|
||||||
|
|
||||||
|
let expandFields: string[] = []
|
||||||
|
if (validated.expand) {
|
||||||
|
expandFields = validated.expand.split(",")
|
||||||
|
}
|
||||||
|
|
||||||
|
const listConfig: FindConfig<PriceList> = {
|
||||||
|
relations: expandFields,
|
||||||
|
skip: validated.offset,
|
||||||
|
take: validated.limit,
|
||||||
|
order: { created_at: "DESC" } as { [k: string]: "DESC" },
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof validated.order !== "undefined") {
|
||||||
|
if (validated.order.startsWith("-")) {
|
||||||
|
const [, field] = validated.order.split("-")
|
||||||
|
listConfig.order = { [field]: "DESC" }
|
||||||
|
} else {
|
||||||
|
listConfig.order = { [validated.order]: "ASC" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const filterableFields = omit(validated, [
|
||||||
|
"limit",
|
||||||
|
"offset",
|
||||||
|
"expand",
|
||||||
|
"order",
|
||||||
|
])
|
||||||
|
|
||||||
|
const [price_lists, count] = await priceListService.listAndCount(
|
||||||
|
filterableFields,
|
||||||
|
listConfig
|
||||||
|
)
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
price_lists,
|
||||||
|
count,
|
||||||
|
offset: validated.offset,
|
||||||
|
limit: validated.limit,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AdminGetPriceListPaginationParams extends FilterablePriceListProps {
|
||||||
|
@IsNumber()
|
||||||
|
@IsOptional()
|
||||||
|
@Type(() => Number)
|
||||||
|
offset?: number = 0
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
@IsOptional()
|
||||||
|
@Type(() => Number)
|
||||||
|
limit?: number = 10
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
expand?: string
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
order?: string
|
||||||
|
}
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
import { Type } from "class-transformer"
|
||||||
|
import {
|
||||||
|
IsArray,
|
||||||
|
IsEnum,
|
||||||
|
IsOptional,
|
||||||
|
IsString,
|
||||||
|
ValidateNested,
|
||||||
|
} from "class-validator"
|
||||||
|
import { defaultAdminPriceListFields, defaultAdminPriceListRelations } from "."
|
||||||
|
import { PriceList } from "../../../.."
|
||||||
|
import PriceListService from "../../../../services/price-list"
|
||||||
|
import {
|
||||||
|
AdminPriceListPricesUpdateReq,
|
||||||
|
PriceListStatus,
|
||||||
|
PriceListType,
|
||||||
|
} from "../../../../types/price-list"
|
||||||
|
import { validator } from "../../../../utils/validator"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @oas [post] /price_lists/{id}
|
||||||
|
* operationId: "PostPriceListsPriceListPriceList"
|
||||||
|
* summary: "Update a Price List"
|
||||||
|
* description: "Updates a Price List"
|
||||||
|
* x-authenticated: true
|
||||||
|
* parameters:
|
||||||
|
* - (path) id=* {string} The id of the Price List.
|
||||||
|
* requestBody:
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* properties:
|
||||||
|
* name:
|
||||||
|
* description: "The name of the Price List"
|
||||||
|
* type: string
|
||||||
|
* description:
|
||||||
|
* description: "A description of the Price List."
|
||||||
|
* type: string
|
||||||
|
* type:
|
||||||
|
* description: The type of the Price List.
|
||||||
|
* type: string
|
||||||
|
* enum:
|
||||||
|
* - sale
|
||||||
|
* - override
|
||||||
|
* status:
|
||||||
|
* description: The status of the Price List.
|
||||||
|
* type: string
|
||||||
|
* enum:
|
||||||
|
* - active
|
||||||
|
* - draft
|
||||||
|
* prices:
|
||||||
|
* description: The prices of the Price List.
|
||||||
|
* type: array
|
||||||
|
* items:
|
||||||
|
* properties:
|
||||||
|
* id:
|
||||||
|
* description: The id of the price.
|
||||||
|
* type: string
|
||||||
|
* region_id:
|
||||||
|
* description: The id of the Region for which the price is used.
|
||||||
|
* type: string
|
||||||
|
* currency_code:
|
||||||
|
* description: The 3 character ISO currency code for which the price will be used.
|
||||||
|
* type: string
|
||||||
|
* amount:
|
||||||
|
* description: The amount to charge for the Product Variant.
|
||||||
|
* type: integer
|
||||||
|
* min_quantity:
|
||||||
|
* description: The minimum quantity for which the price will be used.
|
||||||
|
* type: integer
|
||||||
|
* max_quantity:
|
||||||
|
* description: The maximum quantity for which the price will be used.
|
||||||
|
* type: integer
|
||||||
|
* customer_groups:
|
||||||
|
* type: array
|
||||||
|
* description: A list of customer groups that the Price List applies to.
|
||||||
|
* items:
|
||||||
|
* required:
|
||||||
|
* - id
|
||||||
|
* properties:
|
||||||
|
* id:
|
||||||
|
* description: The id of a customer group
|
||||||
|
* type: string
|
||||||
|
* tags:
|
||||||
|
* - Price List
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: OK
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* properties:
|
||||||
|
* product:
|
||||||
|
* $ref: "#/components/schemas/price_list"
|
||||||
|
*/
|
||||||
|
export default async (req, res) => {
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
const validated = await validator(
|
||||||
|
AdminPostPriceListsPriceListPriceListReq,
|
||||||
|
req.body
|
||||||
|
)
|
||||||
|
|
||||||
|
const priceListService: PriceListService =
|
||||||
|
req.scope.resolve("priceListService")
|
||||||
|
|
||||||
|
await priceListService.update(id, validated)
|
||||||
|
|
||||||
|
const priceList = await priceListService.retrieve(id, {
|
||||||
|
select: defaultAdminPriceListFields as (keyof PriceList)[],
|
||||||
|
relations: defaultAdminPriceListRelations,
|
||||||
|
})
|
||||||
|
|
||||||
|
res.json({ price_list: priceList })
|
||||||
|
}
|
||||||
|
|
||||||
|
class CustomerGroup {
|
||||||
|
@IsString()
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AdminPostPriceListsPriceListPriceListReq {
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
name?: string
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
description?: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
starts_at?: Date
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
ends_at?: Date
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsEnum(PriceListStatus)
|
||||||
|
status?: PriceListStatus
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsEnum(PriceListType)
|
||||||
|
type?: PriceListType
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsArray()
|
||||||
|
@Type(() => AdminPriceListPricesUpdateReq)
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
prices: AdminPriceListPricesUpdateReq[]
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsArray()
|
||||||
|
@Type(() => CustomerGroup)
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
customer_groups: CustomerGroup[]
|
||||||
|
}
|
||||||
@@ -3,12 +3,10 @@ import {
|
|||||||
IsArray,
|
IsArray,
|
||||||
IsBoolean,
|
IsBoolean,
|
||||||
IsEnum,
|
IsEnum,
|
||||||
IsInt,
|
|
||||||
IsNumber,
|
IsNumber,
|
||||||
IsObject,
|
IsObject,
|
||||||
IsOptional,
|
IsOptional,
|
||||||
IsString,
|
IsString,
|
||||||
Validate,
|
|
||||||
ValidateNested,
|
ValidateNested,
|
||||||
} from "class-validator"
|
} from "class-validator"
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
@@ -19,7 +17,7 @@ import {
|
|||||||
ShippingProfileService,
|
ShippingProfileService,
|
||||||
} from "../../../../services"
|
} from "../../../../services"
|
||||||
import { ProductStatus } from "../../../../types/product"
|
import { ProductStatus } from "../../../../types/product"
|
||||||
import { XorConstraint } from "../../../../types/validators/xor"
|
import { ProductVariantPricesCreateReq } from "../../../../types/product-variant"
|
||||||
import { validator } from "../../../../utils/validator"
|
import { validator } from "../../../../utils/validator"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -308,21 +306,6 @@ class ProductOptionReq {
|
|||||||
title: string
|
title: string
|
||||||
}
|
}
|
||||||
|
|
||||||
class ProductVariantPricesReq {
|
|
||||||
@Validate(XorConstraint, ["currency_code"])
|
|
||||||
region_id?: string
|
|
||||||
|
|
||||||
@Validate(XorConstraint, ["region_id"])
|
|
||||||
currency_code?: string
|
|
||||||
|
|
||||||
@IsInt()
|
|
||||||
amount: number
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsInt()
|
|
||||||
sale_amount?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
class ProductVariantReq {
|
class ProductVariantReq {
|
||||||
@IsString()
|
@IsString()
|
||||||
title: string
|
title: string
|
||||||
@@ -393,8 +376,8 @@ class ProductVariantReq {
|
|||||||
|
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@ValidateNested({ each: true })
|
@ValidateNested({ each: true })
|
||||||
@Type(() => ProductVariantPricesReq)
|
@Type(() => ProductVariantPricesCreateReq)
|
||||||
prices: ProductVariantPricesReq[]
|
prices: ProductVariantPricesCreateReq[]
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@Type(() => ProductVariantOptionReq)
|
@Type(() => ProductVariantOptionReq)
|
||||||
|
|||||||
@@ -2,17 +2,15 @@ import { Type } from "class-transformer"
|
|||||||
import {
|
import {
|
||||||
IsArray,
|
IsArray,
|
||||||
IsBoolean,
|
IsBoolean,
|
||||||
IsInt,
|
|
||||||
IsNumber,
|
IsNumber,
|
||||||
IsObject,
|
IsObject,
|
||||||
IsOptional,
|
IsOptional,
|
||||||
IsString,
|
IsString,
|
||||||
Validate,
|
|
||||||
ValidateNested,
|
ValidateNested,
|
||||||
} from "class-validator"
|
} from "class-validator"
|
||||||
import { defaultAdminProductFields, defaultAdminProductRelations } from "."
|
import { defaultAdminProductFields, defaultAdminProductRelations } from "."
|
||||||
import { ProductService, ProductVariantService } from "../../../../services"
|
import { ProductService, ProductVariantService } from "../../../../services"
|
||||||
import { XorConstraint } from "../../../../types/validators/xor"
|
import { ProductVariantPricesCreateReq } from "../../../../types/product-variant"
|
||||||
import { validator } from "../../../../utils/validator"
|
import { validator } from "../../../../utils/validator"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -96,8 +94,11 @@ import { validator } from "../../../../utils/validator"
|
|||||||
* amount:
|
* amount:
|
||||||
* description: The amount to charge for the Product Variant.
|
* description: The amount to charge for the Product Variant.
|
||||||
* type: integer
|
* type: integer
|
||||||
* sale_amount:
|
* min_quantity:
|
||||||
* description: The sale amount to charge for the Product Variant.
|
* description: The minimum quantity for which the price will be used.
|
||||||
|
* type: integer
|
||||||
|
* max_quantity:
|
||||||
|
* description: The maximum quantity for which the price will be used.
|
||||||
* type: integer
|
* type: integer
|
||||||
* options:
|
* options:
|
||||||
* type: array
|
* type: array
|
||||||
@@ -152,21 +153,6 @@ class ProductVariantOptionReq {
|
|||||||
option_id: string
|
option_id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
class ProductVariantPricesReq {
|
|
||||||
@Validate(XorConstraint, ["currency_code"])
|
|
||||||
region_id?: string
|
|
||||||
|
|
||||||
@Validate(XorConstraint, ["region_id"])
|
|
||||||
currency_code?: string
|
|
||||||
|
|
||||||
@IsInt()
|
|
||||||
amount: number
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsInt()
|
|
||||||
sale_amount?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AdminPostProductsProductVariantsReq {
|
export class AdminPostProductsProductVariantsReq {
|
||||||
@IsString()
|
@IsString()
|
||||||
title: string
|
title: string
|
||||||
@@ -237,8 +223,8 @@ export class AdminPostProductsProductVariantsReq {
|
|||||||
|
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@ValidateNested({ each: true })
|
@ValidateNested({ each: true })
|
||||||
@Type(() => ProductVariantPricesReq)
|
@Type(() => ProductVariantPricesCreateReq)
|
||||||
prices: ProductVariantPricesReq[]
|
prices: ProductVariantPricesCreateReq[]
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@Type(() => ProductVariantOptionReq)
|
@Type(() => ProductVariantOptionReq)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Router } from "express"
|
import { Router } from "express"
|
||||||
import { Product, ProductTag, ProductType } from "../../../.."
|
|
||||||
import { DeleteResponse, PaginatedResponse } from "../../../../types/common"
|
|
||||||
import middlewares from "../../../middlewares"
|
|
||||||
import "reflect-metadata"
|
import "reflect-metadata"
|
||||||
|
import { Product, ProductTag, ProductType } from "../../../.."
|
||||||
|
import { PaginatedResponse } from "../../../../types/common"
|
||||||
|
import middlewares from "../../../middlewares"
|
||||||
|
|
||||||
const route = Router()
|
const route = Router()
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
IsOptional,
|
IsOptional,
|
||||||
IsString,
|
IsString,
|
||||||
NotEquals,
|
NotEquals,
|
||||||
Validate,
|
|
||||||
ValidateIf,
|
ValidateIf,
|
||||||
ValidateNested,
|
ValidateNested,
|
||||||
} from "class-validator"
|
} from "class-validator"
|
||||||
@@ -19,7 +18,7 @@ import {
|
|||||||
ProductStatus,
|
ProductStatus,
|
||||||
} from "."
|
} from "."
|
||||||
import { ProductService } from "../../../../services"
|
import { ProductService } from "../../../../services"
|
||||||
import { XorConstraint } from "../../../../types/validators/xor"
|
import { ProductVariantPricesUpdateReq } from "../../../../types/product-variant"
|
||||||
import { validator } from "../../../../utils/validator"
|
import { validator } from "../../../../utils/validator"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -248,21 +247,6 @@ class ProductVariantOptionReq {
|
|||||||
option_id: string
|
option_id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
class ProductVariantPricesReq {
|
|
||||||
@Validate(XorConstraint, ["currency_code"])
|
|
||||||
region_id?: string
|
|
||||||
|
|
||||||
@Validate(XorConstraint, ["region_id"])
|
|
||||||
currency_code?: string
|
|
||||||
|
|
||||||
@IsInt()
|
|
||||||
amount: number
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsInt()
|
|
||||||
sale_amount?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
class ProductVariantReq {
|
class ProductVariantReq {
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@@ -339,8 +323,8 @@ class ProductVariantReq {
|
|||||||
@IsArray()
|
@IsArray()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@ValidateNested({ each: true })
|
@ValidateNested({ each: true })
|
||||||
@Type(() => ProductVariantPricesReq)
|
@Type(() => ProductVariantPricesUpdateReq)
|
||||||
prices: ProductVariantPricesReq[]
|
prices: ProductVariantPricesUpdateReq[]
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@Type(() => ProductVariantOptionReq)
|
@Type(() => ProductVariantOptionReq)
|
||||||
|
|||||||
@@ -2,17 +2,15 @@ import { Type } from "class-transformer"
|
|||||||
import {
|
import {
|
||||||
IsArray,
|
IsArray,
|
||||||
IsBoolean,
|
IsBoolean,
|
||||||
IsInt,
|
|
||||||
IsNumber,
|
IsNumber,
|
||||||
IsObject,
|
IsObject,
|
||||||
IsOptional,
|
IsOptional,
|
||||||
IsString,
|
IsString,
|
||||||
Validate,
|
|
||||||
ValidateNested,
|
ValidateNested,
|
||||||
} from "class-validator"
|
} from "class-validator"
|
||||||
import { defaultAdminProductFields, defaultAdminProductRelations } from "."
|
import { defaultAdminProductFields, defaultAdminProductRelations } from "."
|
||||||
import { ProductService, ProductVariantService } from "../../../../services"
|
import { ProductService, ProductVariantService } from "../../../../services"
|
||||||
import { XorConstraint } from "../../../../types/validators/xor"
|
import { ProductVariantPricesUpdateReq } from "../../../../types/product-variant"
|
||||||
import { validator } from "../../../../utils/validator"
|
import { validator } from "../../../../utils/validator"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -84,6 +82,9 @@ import { validator } from "../../../../utils/validator"
|
|||||||
* type: array
|
* type: array
|
||||||
* items:
|
* items:
|
||||||
* properties:
|
* properties:
|
||||||
|
* id:
|
||||||
|
* description: The id of the price.
|
||||||
|
* type: string
|
||||||
* region_id:
|
* region_id:
|
||||||
* description: The id of the Region for which the price is used.
|
* description: The id of the Region for which the price is used.
|
||||||
* type: string
|
* type: string
|
||||||
@@ -93,8 +94,11 @@ import { validator } from "../../../../utils/validator"
|
|||||||
* amount:
|
* amount:
|
||||||
* description: The amount to charge for the Product Variant.
|
* description: The amount to charge for the Product Variant.
|
||||||
* type: integer
|
* type: integer
|
||||||
* sale_amount:
|
* min_quantity:
|
||||||
* description: The sale amount to charge for the Product Variant.
|
* description: The minimum quantity for which the price will be used.
|
||||||
|
* type: integer
|
||||||
|
* max_quantity:
|
||||||
|
* description: The maximum quantity for which the price will be used.
|
||||||
* type: integer
|
* type: integer
|
||||||
* options:
|
* options:
|
||||||
* type: array
|
* type: array
|
||||||
@@ -152,21 +156,6 @@ class ProductVariantOptionReq {
|
|||||||
option_id: string
|
option_id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
class ProductVariantPricesReq {
|
|
||||||
@Validate(XorConstraint, ["currency_code"])
|
|
||||||
region_id?: string
|
|
||||||
|
|
||||||
@Validate(XorConstraint, ["region_id"])
|
|
||||||
currency_code?: string
|
|
||||||
|
|
||||||
@IsInt()
|
|
||||||
amount: number
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsInt()
|
|
||||||
sale_amount?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AdminPostProductsProductVariantsVariantReq {
|
export class AdminPostProductsProductVariantsVariantReq {
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@@ -238,8 +227,8 @@ export class AdminPostProductsProductVariantsVariantReq {
|
|||||||
|
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@ValidateNested({ each: true })
|
@ValidateNested({ each: true })
|
||||||
@Type(() => ProductVariantPricesReq)
|
@Type(() => ProductVariantPricesUpdateReq)
|
||||||
prices: ProductVariantPricesReq[]
|
prices: ProductVariantPricesUpdateReq[]
|
||||||
|
|
||||||
@Type(() => ProductVariantOptionReq)
|
@Type(() => ProductVariantOptionReq)
|
||||||
@ValidateNested({ each: true })
|
@ValidateNested({ each: true })
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ export * from "./models/order"
|
|||||||
export * from "./models/payment"
|
export * from "./models/payment"
|
||||||
export * from "./models/payment-provider"
|
export * from "./models/payment-provider"
|
||||||
export * from "./models/payment-session"
|
export * from "./models/payment-session"
|
||||||
|
export * from "./models/price-list"
|
||||||
export * from "./models/product"
|
export * from "./models/product"
|
||||||
export * from "./models/product-collection"
|
export * from "./models/product-collection"
|
||||||
export * from "./models/product-option"
|
export * from "./models/product-option"
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
export class updateMoneyAmountAddPriceList1646915480108 implements MigrationInterface {
|
||||||
|
name = 'updateMoneyAmountAddPriceList1646915480108'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`CREATE TYPE "price_list_type_enum" AS ENUM('sale', 'override')`);
|
||||||
|
await queryRunner.query(`CREATE TYPE "price_list_status_enum" AS ENUM('active', 'draft')`);
|
||||||
|
await queryRunner.query(`CREATE TABLE "price_list" ("id" character varying NOT NULL, "name" character varying NOT NULL, "description" character varying NOT NULL, "type" "price_list_type_enum" NOT NULL DEFAULT 'sale', "status" "price_list_status_enum" NOT NULL DEFAULT 'draft', "starts_at" TIMESTAMP WITH TIME ZONE, "ends_at" TIMESTAMP WITH TIME ZONE, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP WITH TIME ZONE, CONSTRAINT "PK_52ea7826468b1c889cb2c28df03" PRIMARY KEY ("id"))`);
|
||||||
|
await queryRunner.query(`CREATE TABLE "price_list_customer_groups" ("price_list_id" character varying NOT NULL, "customer_group_id" character varying NOT NULL, CONSTRAINT "PK_1afcbe15cc8782dc80c05707df9" PRIMARY KEY ("price_list_id", "customer_group_id"))`);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_52875734e9dd69064f0041f4d9" ON "price_list_customer_groups" ("price_list_id") `);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_c5516f550433c9b1c2630d787a" ON "price_list_customer_groups" ("customer_group_id") `);
|
||||||
|
await queryRunner.query(`ALTER TABLE "money_amount" DROP COLUMN "sale_amount"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "money_amount" ADD "min_quantity" integer`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "money_amount" ADD "max_quantity" integer`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "money_amount" ADD "price_list_id" character varying`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "money_amount" ADD CONSTRAINT "FK_f249976b079375499662eb80c40" FOREIGN KEY ("price_list_id") REFERENCES "price_list"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "price_list_customer_groups" ADD CONSTRAINT "FK_52875734e9dd69064f0041f4d92" FOREIGN KEY ("price_list_id") REFERENCES "price_list"("id") ON DELETE CASCADE ON UPDATE CASCADE`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "price_list_customer_groups" ADD CONSTRAINT "FK_c5516f550433c9b1c2630d787a7" FOREIGN KEY ("customer_group_id") REFERENCES "customer_group"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "price_list_customer_groups" DROP CONSTRAINT "FK_c5516f550433c9b1c2630d787a7"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "price_list_customer_groups" DROP CONSTRAINT "FK_52875734e9dd69064f0041f4d92"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "money_amount" DROP CONSTRAINT "FK_f249976b079375499662eb80c40"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "money_amount" DROP COLUMN "price_list_id"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "money_amount" DROP COLUMN "max_quantity"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "money_amount" DROP COLUMN "min_quantity"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "money_amount" ADD "sale_amount" integer`);
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_c5516f550433c9b1c2630d787a"`);
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_52875734e9dd69064f0041f4d9"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "price_list_customer_groups"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "price_list"`);
|
||||||
|
await queryRunner.query(`DROP TYPE "price_list_status_enum"`);
|
||||||
|
await queryRunner.query(`DROP TYPE "price_list_type_enum"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -5,14 +5,14 @@ import {
|
|||||||
DeleteDateColumn,
|
DeleteDateColumn,
|
||||||
Entity,
|
Entity,
|
||||||
Index,
|
Index,
|
||||||
JoinTable,
|
|
||||||
ManyToMany,
|
ManyToMany,
|
||||||
PrimaryColumn,
|
PrimaryColumn,
|
||||||
UpdateDateColumn,
|
UpdateDateColumn
|
||||||
} from "typeorm"
|
} from "typeorm"
|
||||||
import { ulid } from "ulid"
|
import { ulid } from "ulid"
|
||||||
import { Customer } from ".."
|
|
||||||
import { DbAwareColumn, resolveDbType } from "../utils/db-aware-column"
|
import { DbAwareColumn, resolveDbType } from "../utils/db-aware-column"
|
||||||
|
import { Customer } from "./customer"
|
||||||
|
import { PriceList } from "./price-list"
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class CustomerGroup {
|
export class CustomerGroup {
|
||||||
@@ -23,22 +23,22 @@ export class CustomerGroup {
|
|||||||
@Column()
|
@Column()
|
||||||
name: string
|
name: string
|
||||||
|
|
||||||
@ManyToMany(() => Customer, (customer) => customer.groups, {
|
@ManyToMany(
|
||||||
onDelete: "CASCADE",
|
() => Customer,
|
||||||
})
|
(customer) => customer.groups,
|
||||||
@JoinTable({
|
{
|
||||||
name: "customer_group_customers",
|
onDelete: "CASCADE",
|
||||||
joinColumn: {
|
}
|
||||||
name: "customer_group_id",
|
)
|
||||||
referencedColumnName: "id",
|
|
||||||
},
|
|
||||||
inverseJoinColumn: {
|
|
||||||
name: "customer_id",
|
|
||||||
referencedColumnName: "id",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
customers: Customer[]
|
customers: Customer[]
|
||||||
|
|
||||||
|
@ManyToMany(
|
||||||
|
() => PriceList,
|
||||||
|
(priceList) => priceList.customer_groups,
|
||||||
|
{ onDelete: "CASCADE" }
|
||||||
|
)
|
||||||
|
price_lists: PriceList[]
|
||||||
|
|
||||||
@CreateDateColumn({ type: resolveDbType("timestamptz") })
|
@CreateDateColumn({ type: resolveDbType("timestamptz") })
|
||||||
created_at: Date
|
created_at: Date
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,12 @@ 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"
|
||||||
import { Currency } from "./currency"
|
import { Currency } from "./currency"
|
||||||
|
import { PriceList } from "./price-list"
|
||||||
import { ProductVariant } from "./product-variant"
|
import { ProductVariant } from "./product-variant"
|
||||||
import { Region } from "./region"
|
import { Region } from "./region"
|
||||||
|
|
||||||
@@ -31,16 +32,34 @@ export class MoneyAmount {
|
|||||||
@Column({ type: "int" })
|
@Column({ type: "int" })
|
||||||
amount: number
|
amount: number
|
||||||
|
|
||||||
@Column({ type: "int", nullable: true, default: null })
|
@Column({ type: "int", nullable: true })
|
||||||
sale_amount?: number
|
min_quantity: number | null
|
||||||
|
|
||||||
|
@Column({ type: "int", nullable: true })
|
||||||
|
max_quantity: number | null
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
price_list_id: string | null
|
||||||
|
|
||||||
|
@ManyToOne(
|
||||||
|
() => PriceList,
|
||||||
|
(priceList) => priceList.prices,
|
||||||
|
{ cascade: true, onDelete: "CASCADE" }
|
||||||
|
)
|
||||||
|
@JoinColumn({ name: "price_list_id" })
|
||||||
|
price_list: PriceList | null
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
variant_id: string
|
variant_id: string
|
||||||
|
|
||||||
@ManyToOne(() => ProductVariant, (variant) => variant.prices, {
|
@ManyToOne(
|
||||||
onDelete: "CASCADE",
|
() => ProductVariant,
|
||||||
})
|
(variant) => variant.prices,
|
||||||
|
{
|
||||||
|
onDelete: "CASCADE",
|
||||||
|
}
|
||||||
|
)
|
||||||
@JoinColumn({ name: "variant_id" })
|
@JoinColumn({ name: "variant_id" })
|
||||||
variant: ProductVariant
|
variant: ProductVariant
|
||||||
|
|
||||||
@@ -86,8 +105,11 @@ export class MoneyAmount {
|
|||||||
* amount:
|
* amount:
|
||||||
* description: "The amount in the smallest currecny unit (e.g. cents 100 cents to charge $1) that the Product Variant will cost."
|
* description: "The amount in the smallest currecny unit (e.g. cents 100 cents to charge $1) that the Product Variant will cost."
|
||||||
* type: integer
|
* type: integer
|
||||||
* sale_amount:
|
* min_quantity:
|
||||||
* description: "An optional sale amount that the Product Variant will be available for when defined."
|
* description: "The minimum quantity that the Money Amount applies to. If this value is not set, the Money Amount applies to all quantities."
|
||||||
|
* type: integer
|
||||||
|
* max_quantity:
|
||||||
|
* description: "The maximum quantity that the Money Amount applies to. If this value is not set, the Money Amount applies to all quantities."
|
||||||
* type: integer
|
* type: integer
|
||||||
* variant_id:
|
* variant_id:
|
||||||
* description: "The id of the Product Variant that the Money Amount belongs to."
|
* description: "The id of the Product Variant that the Money Amount belongs to."
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
import {
|
||||||
|
BeforeInsert,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
DeleteDateColumn,
|
||||||
|
Entity,
|
||||||
|
JoinTable,
|
||||||
|
ManyToMany,
|
||||||
|
OneToMany,
|
||||||
|
PrimaryColumn,
|
||||||
|
UpdateDateColumn
|
||||||
|
} from "typeorm"
|
||||||
|
import { ulid } from "ulid"
|
||||||
|
import { PriceListStatus, PriceListType } from "../types/price-list"
|
||||||
|
import { DbAwareColumn, resolveDbType } from "../utils/db-aware-column"
|
||||||
|
import { CustomerGroup } from "./customer-group"
|
||||||
|
import { MoneyAmount } from "./money-amount"
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class PriceList {
|
||||||
|
@PrimaryColumn()
|
||||||
|
id: string
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
name: string
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
description: string
|
||||||
|
|
||||||
|
@DbAwareColumn({ type: "enum", enum: PriceListType, default: "sale" })
|
||||||
|
type: PriceListType
|
||||||
|
|
||||||
|
@DbAwareColumn({ type: "enum", enum: PriceListStatus, default: "draft" })
|
||||||
|
status: PriceListStatus
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: resolveDbType("timestamptz"),
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
starts_at: Date
|
||||||
|
|
||||||
|
@Column({ type: resolveDbType("timestamptz"), nullable: true })
|
||||||
|
ends_at: Date
|
||||||
|
|
||||||
|
@JoinTable({
|
||||||
|
name: "price_list_customer_groups",
|
||||||
|
joinColumn: {
|
||||||
|
name: "price_list_id",
|
||||||
|
referencedColumnName: "id",
|
||||||
|
},
|
||||||
|
inverseJoinColumn: {
|
||||||
|
name: "customer_group_id",
|
||||||
|
referencedColumnName: "id",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
@ManyToMany(
|
||||||
|
() => CustomerGroup,
|
||||||
|
(cg) => cg.price_lists,
|
||||||
|
{ onDelete: "CASCADE" }
|
||||||
|
)
|
||||||
|
customer_groups: CustomerGroup[]
|
||||||
|
|
||||||
|
@OneToMany(
|
||||||
|
() => MoneyAmount,
|
||||||
|
(moneyAmount) => moneyAmount.price_list,
|
||||||
|
{ onDelete: "CASCADE" }
|
||||||
|
)
|
||||||
|
prices: MoneyAmount[]
|
||||||
|
|
||||||
|
@CreateDateColumn({ type: resolveDbType("timestamptz") })
|
||||||
|
created_at: Date
|
||||||
|
|
||||||
|
@UpdateDateColumn({ type: resolveDbType("timestamptz") })
|
||||||
|
updated_at: Date
|
||||||
|
|
||||||
|
@DeleteDateColumn({ type: resolveDbType("timestamptz") })
|
||||||
|
deleted_at: Date
|
||||||
|
|
||||||
|
@BeforeInsert()
|
||||||
|
private beforeInsert(): undefined | void {
|
||||||
|
if (this.id) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const id = ulid()
|
||||||
|
this.id = `pl_${id}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @schema price_list
|
||||||
|
* title: "Price List"
|
||||||
|
* description: "Price Lists represents a set of prices that overrides the default price for one or more product variants."
|
||||||
|
* x-resourceId: price_list
|
||||||
|
* properties:
|
||||||
|
* id:
|
||||||
|
* description: "The id of the Price List. This value will be prefixed by `pl_`."
|
||||||
|
* type: string
|
||||||
|
* type:
|
||||||
|
* description: "The type of Price List. This can be one of either `sale` or `override`."
|
||||||
|
* type: string
|
||||||
|
* enum:
|
||||||
|
* - sale
|
||||||
|
* - override
|
||||||
|
* starts_at:
|
||||||
|
* description: "The date with timezone that the Price List starts being valid."
|
||||||
|
* type: date-time
|
||||||
|
* ends_at:
|
||||||
|
* description: "The date with timezone that the Price List stops being valid."
|
||||||
|
* type: date-time
|
||||||
|
* customer_groups:
|
||||||
|
* description: "The Customer Groups that the Price List applies to."
|
||||||
|
* type: array
|
||||||
|
* items:
|
||||||
|
* $ref: "#/components/schemas/customer_group"
|
||||||
|
* created_at:
|
||||||
|
* description: "The date with timezone at which the resource was created."
|
||||||
|
* type: string
|
||||||
|
* format: date-time
|
||||||
|
* updated_at:
|
||||||
|
* description: "The date with timezone at which the resource was last updated."
|
||||||
|
* type: string
|
||||||
|
* format: date-time
|
||||||
|
* deleted_at:
|
||||||
|
* description: "The date with timezone at which the resource was deleted."
|
||||||
|
* type: string
|
||||||
|
* format: date-time
|
||||||
|
*/
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
|
||||||
|
export const moneyAmounts = {
|
||||||
|
amountOne: {
|
||||||
|
id: IdMap.getId("amountOne"),
|
||||||
|
currency_code: "USD",
|
||||||
|
amount: 1,
|
||||||
|
min_quantity: 1,
|
||||||
|
max_quantity: 10,
|
||||||
|
price_list_id: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MoneyAmountModelMock = {
|
||||||
|
create: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
|
findOne: jest.fn().mockImplementation(query => {
|
||||||
|
if (query._id === IdMap.getId("amountOne")) {
|
||||||
|
return Promise.resolve(moneyAmounts.amountOne)
|
||||||
|
}
|
||||||
|
return Promise.resolve(undefined)
|
||||||
|
}),
|
||||||
|
addToPriceList: jest.fn().mockImplementation((priceListId, prices, overrideExisting) => {
|
||||||
|
return Promise.resolve()
|
||||||
|
}),
|
||||||
|
deletePriceListPrices: jest.fn().mockImplementation((priceListId, moneyAmountIds) => {
|
||||||
|
return Promise.resolve()
|
||||||
|
}),
|
||||||
|
updatePriceListPrices: jest.fn().mockImplementation((priceListId, updates) => {
|
||||||
|
return Promise.resolve()
|
||||||
|
}),
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
export const PriceListModelMock = {
|
||||||
|
create: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
|
updateOne: jest.fn().mockImplementation((query, update) => {
|
||||||
|
return Promise.resolve()
|
||||||
|
}),
|
||||||
|
deleteOne: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
|
findOne: jest.fn().mockImplementation(query => {
|
||||||
|
if (query.email === "oliver@medusa.com") {
|
||||||
|
return Promise.resolve(customers.testCustomer)
|
||||||
|
}
|
||||||
|
if (query.phone === "12345678") {
|
||||||
|
return Promise.resolve(customers.customerWithPhone)
|
||||||
|
}
|
||||||
|
if (query._id === IdMap.getId("testCustomer")) {
|
||||||
|
return Promise.resolve(customers.testCustomer)
|
||||||
|
}
|
||||||
|
if (query._id === IdMap.getId("deleteId")) {
|
||||||
|
return Promise.resolve(customers.deleteCustomer)
|
||||||
|
}
|
||||||
|
return Promise.resolve(undefined)
|
||||||
|
}),
|
||||||
|
}
|
||||||
@@ -1,19 +1,20 @@
|
|||||||
|
import partition from "lodash/partition"
|
||||||
import {
|
import {
|
||||||
Brackets,
|
Brackets,
|
||||||
EntityRepository,
|
EntityRepository,
|
||||||
In,
|
In,
|
||||||
IsNull,
|
IsNull,
|
||||||
Not,
|
Not,
|
||||||
Repository,
|
Repository
|
||||||
} from "typeorm"
|
} from "typeorm"
|
||||||
import { MoneyAmount } from "../models/money-amount"
|
import { MoneyAmount } from "../models/money-amount"
|
||||||
|
import { PriceListPriceCreateInput, PriceListPriceUpdateInput } from "../types/price-list"
|
||||||
|
|
||||||
type Price = Partial<
|
type Price = Partial<
|
||||||
Pick<
|
Omit<MoneyAmount, "created_at" | "updated_at" | "deleted_at">
|
||||||
MoneyAmount,
|
> & {
|
||||||
"currency_code" | "region_id" | "sale_amount" | "currency_code"
|
amount: number
|
||||||
>
|
}
|
||||||
> & { amount: number }
|
|
||||||
|
|
||||||
@EntityRepository(MoneyAmount)
|
@EntityRepository(MoneyAmount)
|
||||||
export class MoneyAmountRepository extends Repository<MoneyAmount> {
|
export class MoneyAmountRepository extends Repository<MoneyAmount> {
|
||||||
@@ -21,6 +22,7 @@ export class MoneyAmountRepository extends Repository<MoneyAmount> {
|
|||||||
const pricesNotInPricesPayload = await this.createQueryBuilder()
|
const pricesNotInPricesPayload = await this.createQueryBuilder()
|
||||||
.where({
|
.where({
|
||||||
variant_id: variantId,
|
variant_id: variantId,
|
||||||
|
price_list_id: IsNull(),
|
||||||
})
|
})
|
||||||
.andWhere(
|
.andWhere(
|
||||||
new Brackets((qb) => {
|
new Brackets((qb) => {
|
||||||
@@ -33,12 +35,13 @@ export class MoneyAmountRepository extends Repository<MoneyAmount> {
|
|||||||
return pricesNotInPricesPayload
|
return pricesNotInPricesPayload
|
||||||
}
|
}
|
||||||
|
|
||||||
public async upsertCurrencyPrice(variantId: string, price: Price) {
|
public async upsertVariantCurrencyPrice(variantId: string, price: Price) {
|
||||||
let moneyAmount = await this.findOne({
|
let moneyAmount = await this.findOne({
|
||||||
where: {
|
where: {
|
||||||
currency_code: price.currency_code,
|
currency_code: price.currency_code,
|
||||||
variant_id: variantId,
|
variant_id: variantId,
|
||||||
region_id: IsNull(),
|
region_id: IsNull(),
|
||||||
|
price_list_id: IsNull(),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -50,9 +53,64 @@ export class MoneyAmountRepository extends Repository<MoneyAmount> {
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
moneyAmount.amount = price.amount
|
moneyAmount.amount = price.amount
|
||||||
moneyAmount.sale_amount = price.sale_amount
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.save(moneyAmount)
|
return await this.save(moneyAmount)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async addPriceListPrices(
|
||||||
|
priceListId: string,
|
||||||
|
prices: PriceListPriceCreateInput[],
|
||||||
|
overrideExisting: boolean = false
|
||||||
|
): Promise<MoneyAmount[]> {
|
||||||
|
const toInsert = prices.map((price) => (this.create({
|
||||||
|
...price,
|
||||||
|
price_list_id: priceListId,
|
||||||
|
})))
|
||||||
|
const insertResult = await this.createQueryBuilder()
|
||||||
|
.insert()
|
||||||
|
.orIgnore(true)
|
||||||
|
.into(MoneyAmount)
|
||||||
|
.values(toInsert)
|
||||||
|
.execute()
|
||||||
|
|
||||||
|
if (overrideExisting) {
|
||||||
|
await this.createQueryBuilder()
|
||||||
|
.delete()
|
||||||
|
.from(MoneyAmount)
|
||||||
|
.where({
|
||||||
|
price_list_id: priceListId,
|
||||||
|
id: Not(In(insertResult.identifiers.map((ma) => ma.id))),
|
||||||
|
})
|
||||||
|
.execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.manager
|
||||||
|
.createQueryBuilder(MoneyAmount, "ma")
|
||||||
|
.select()
|
||||||
|
.where(insertResult.identifiers)
|
||||||
|
.getMany()
|
||||||
|
}
|
||||||
|
|
||||||
|
public async deletePriceListPrices(
|
||||||
|
priceListId: string,
|
||||||
|
moneyAmountIds: string[]
|
||||||
|
): Promise<void> {
|
||||||
|
await this.createQueryBuilder()
|
||||||
|
.delete()
|
||||||
|
.from(MoneyAmount)
|
||||||
|
.where({ price_list_id: priceListId, id: In(moneyAmountIds) })
|
||||||
|
.execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updatePriceListPrices(
|
||||||
|
priceListId: string,
|
||||||
|
updates: PriceListPriceUpdateInput[]
|
||||||
|
): Promise<MoneyAmount[]> {
|
||||||
|
const [existingPrices, newPrices] = partition(updates, (update) => update.id)
|
||||||
|
|
||||||
|
const newPriceEntities = newPrices.map((price) => (this.create({ ...price, price_list_id: priceListId })))
|
||||||
|
|
||||||
|
return await this.save([...existingPrices, ...newPriceEntities])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { EntityRepository, Repository } from "typeorm"
|
||||||
|
import { PriceList } from "../models/price-list"
|
||||||
|
|
||||||
|
@EntityRepository(PriceList)
|
||||||
|
export class PriceListRepository extends Repository<PriceList> {}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
export const PriceListServiceMock = {
|
||||||
|
withTransaction: function() {
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
|
||||||
|
create: jest.fn().mockImplementation((f) => {
|
||||||
|
return Promise.resolve(f)
|
||||||
|
}),
|
||||||
|
|
||||||
|
retrieve: jest.fn().mockImplementation((f) => {
|
||||||
|
return Promise.resolve(f)
|
||||||
|
}),
|
||||||
|
|
||||||
|
update: jest.fn().mockImplementation((id, update) => {
|
||||||
|
return Promise.resolve({ id, ...update })
|
||||||
|
}),
|
||||||
|
|
||||||
|
delete: jest.fn().mockImplementation((id) => {
|
||||||
|
return Promise.resolve(id)
|
||||||
|
}),
|
||||||
|
|
||||||
|
addPrices: jest.fn().mockImplementation((id, prices) => {
|
||||||
|
return Promise.resolve({ id, prices })
|
||||||
|
}),
|
||||||
|
|
||||||
|
deletePrices: jest.fn().mockImplementation((id, prices) => {
|
||||||
|
return Promise.resolve({ id, prices })
|
||||||
|
}),
|
||||||
|
|
||||||
|
listAndCount: jest.fn().mockImplementation((fields, config) => {
|
||||||
|
return Promise.resolve([[{ id: "pl_1" }, { id: "pl_2" }], 2])
|
||||||
|
}),
|
||||||
|
|
||||||
|
upsertCustomerGroups_: jest.fn().mockImplementation((id, groups) => {
|
||||||
|
return Promise.resolve()
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
const mock = jest.fn().mockImplementation(() => {
|
||||||
|
return PriceListServiceMock
|
||||||
|
})
|
||||||
|
|
||||||
|
export default mock
|
||||||
@@ -98,7 +98,9 @@ const variantWithPrices = {
|
|||||||
id: "price_1",
|
id: "price_1",
|
||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
amount: 100,
|
amount: 100,
|
||||||
sale_amount: null,
|
price_list_id: null,
|
||||||
|
min_quantity: 1,
|
||||||
|
max_quantity: 10,
|
||||||
variant_id: "variant_with_prices",
|
variant_id: "variant_with_prices",
|
||||||
region_id: null,
|
region_id: null,
|
||||||
created_at: "2021-03-16T21:24:13.657Z",
|
created_at: "2021-03-16T21:24:13.657Z",
|
||||||
@@ -109,7 +111,9 @@ const variantWithPrices = {
|
|||||||
id: "price_2",
|
id: "price_2",
|
||||||
currency_code: "dk",
|
currency_code: "dk",
|
||||||
amount: 100,
|
amount: 100,
|
||||||
sale_amount: null,
|
price_list_id: null,
|
||||||
|
min_quantity: 1,
|
||||||
|
max_quantity: 10,
|
||||||
variant_id: "variant_with_prices",
|
variant_id: "variant_with_prices",
|
||||||
region_id: null,
|
region_id: null,
|
||||||
created_at: "2021-03-16T21:24:13.657Z",
|
created_at: "2021-03-16T21:24:13.657Z",
|
||||||
@@ -285,8 +289,12 @@ export const ProductVariantServiceMock = {
|
|||||||
}),
|
}),
|
||||||
delete: jest.fn().mockReturnValue(Promise.resolve()),
|
delete: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
update: jest.fn().mockReturnValue(Promise.resolve()),
|
update: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
setCurrencyPrice: jest.fn().mockReturnValue(Promise.resolve()),
|
updateVariantPrices: jest.fn().mockImplementation((variantId, prices) => {
|
||||||
setRegionPrice: jest.fn().mockReturnValue(Promise.resolve()),
|
return Promise.resolve({})
|
||||||
|
}),
|
||||||
|
deleteVariantPrices: jest.fn().mockImplementation((variantId, priceIds) => {
|
||||||
|
return Promise.resolve({})
|
||||||
|
}),
|
||||||
updateOptionValue: jest.fn().mockReturnValue(Promise.resolve()),
|
updateOptionValue: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
addOptionValue: jest.fn().mockImplementation((variantId, optionId, value) => {
|
addOptionValue: jest.fn().mockImplementation((variantId, optionId, value) => {
|
||||||
return Promise.resolve({})
|
return Promise.resolve({})
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
import { MedusaError } from "medusa-core-utils"
|
||||||
|
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
|
||||||
|
import PriceListService from "../price-list"
|
||||||
|
|
||||||
|
describe("PriceListService", () => {
|
||||||
|
const priceListRepository = MockRepository({
|
||||||
|
findOne: (q) => {
|
||||||
|
if (q === IdMap.getId("batman")) {
|
||||||
|
return Promise.resolve(undefined)
|
||||||
|
}
|
||||||
|
return Promise.resolve({ id: IdMap.getId("ironman") })
|
||||||
|
},
|
||||||
|
create: (data) => {
|
||||||
|
return Promise.resolve({ id: IdMap.getId("ironman"), ...data })
|
||||||
|
},
|
||||||
|
save: (data) => Promise.resolve(data),
|
||||||
|
})
|
||||||
|
|
||||||
|
const customerGroupService = {
|
||||||
|
retrieve: jest.fn((id) => {
|
||||||
|
if (id === IdMap.getId("group")) {
|
||||||
|
return Promise.resolve({ id: IdMap.getId("group") })
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`CustomerGroup with id ${id} was not found`
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
const moneyAmountRepository = MockRepository()
|
||||||
|
|
||||||
|
moneyAmountRepository.addPriceListPrices = jest.fn(() => Promise.resolve())
|
||||||
|
moneyAmountRepository.removePriceListPrices = jest.fn(() => Promise.resolve())
|
||||||
|
moneyAmountRepository.updatePriceListPrices = jest.fn(() => Promise.resolve())
|
||||||
|
|
||||||
|
const defaultRelations = ["prices", "customer_groups"]
|
||||||
|
|
||||||
|
const priceListService = new PriceListService({
|
||||||
|
manager: MockManager,
|
||||||
|
customerGroupService,
|
||||||
|
priceListRepository,
|
||||||
|
moneyAmountRepository,
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("retrieve", () => {
|
||||||
|
it("successfully retrieves a price list", async () => {
|
||||||
|
const result = await priceListService.retrieve(IdMap.getId("ironman"))
|
||||||
|
|
||||||
|
expect(priceListRepository.findOne).toHaveBeenCalledTimes(1)
|
||||||
|
expect(priceListRepository.findOne).toHaveBeenCalledWith({
|
||||||
|
where: { id: IdMap.getId("ironman") },
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.id).toEqual(IdMap.getId("ironman"))
|
||||||
|
})
|
||||||
|
|
||||||
|
it("fails on non-existing product variant id", async () => {
|
||||||
|
try {
|
||||||
|
await priceListService.retrieve(IdMap.getId("batman"))
|
||||||
|
} catch (error) {
|
||||||
|
expect(error.message).toBe(
|
||||||
|
`Price list with id: ${IdMap.getId("batman")} was not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("create", () => {
|
||||||
|
it("creates a new Price List", async () => {
|
||||||
|
const result = await priceListService.create({
|
||||||
|
name: "VIP winter sale",
|
||||||
|
description: "Winter sale for VIP customers. 25% off selected items.",
|
||||||
|
type: "sale",
|
||||||
|
status: "active",
|
||||||
|
starts_at: "2022-07-01T00:00:00.000Z",
|
||||||
|
ends_at: "2022-07-31T00:00:00.000Z",
|
||||||
|
customer_groups: [{ id: IdMap.getId("group") }],
|
||||||
|
prices: [
|
||||||
|
{
|
||||||
|
amount: 100,
|
||||||
|
currency_code: "usd",
|
||||||
|
min_quantity: 1,
|
||||||
|
max_quantity: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(priceListRepository.create).toHaveBeenCalledTimes(1)
|
||||||
|
expect(priceListRepository.create).toHaveBeenCalledWith({
|
||||||
|
name: "VIP winter sale",
|
||||||
|
description: "Winter sale for VIP customers. 25% off selected items.",
|
||||||
|
type: "sale",
|
||||||
|
status: "active",
|
||||||
|
starts_at: "2022-07-01T00:00:00.000Z",
|
||||||
|
ends_at: "2022-07-31T00:00:00.000Z",
|
||||||
|
})
|
||||||
|
expect(customerGroupService.retrieve).toHaveBeenCalledTimes(1)
|
||||||
|
expect(customerGroupService.retrieve).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("group")
|
||||||
|
)
|
||||||
|
expect(moneyAmountRepository.addPriceListPrices).toHaveBeenCalledTimes(1)
|
||||||
|
expect(moneyAmountRepository.addPriceListPrices).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("ironman"),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
amount: 100,
|
||||||
|
currency_code: "usd",
|
||||||
|
min_quantity: 1,
|
||||||
|
max_quantity: 100,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { IdMap, MockRepository, MockManager } from "medusa-test-utils"
|
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
|
||||||
import idMap from "medusa-test-utils/dist/id-map"
|
|
||||||
import ProductVariantService from "../product-variant"
|
import ProductVariantService from "../product-variant"
|
||||||
|
|
||||||
const eventBusService = {
|
const eventBusService = {
|
||||||
@@ -376,7 +375,6 @@ describe("ProductVariantService", () => {
|
|||||||
{
|
{
|
||||||
currency_code: "dkk",
|
currency_code: "dkk",
|
||||||
amount: 1000,
|
amount: 1000,
|
||||||
sale_amount: 750,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
@@ -388,7 +386,6 @@ describe("ProductVariantService", () => {
|
|||||||
{
|
{
|
||||||
currency_code: "dkk",
|
currency_code: "dkk",
|
||||||
amount: 1000,
|
amount: 1000,
|
||||||
sale_amount: 750,
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -435,6 +432,10 @@ describe("ProductVariantService", () => {
|
|||||||
.fn()
|
.fn()
|
||||||
.mockImplementation(() => Promise.resolve(oldPrices))
|
.mockImplementation(() => Promise.resolve(oldPrices))
|
||||||
|
|
||||||
|
moneyAmountRepository.deleteVariantPrices = jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation((variant_id, price_ids) => Promise.resolve({}))
|
||||||
|
|
||||||
const productVariantRepository = MockRepository({
|
const productVariantRepository = MockRepository({
|
||||||
findOne: (query) => Promise.resolve({ id: IdMap.getId("ironman") }),
|
findOne: (query) => Promise.resolve({ id: IdMap.getId("ironman") }),
|
||||||
})
|
})
|
||||||
@@ -456,114 +457,9 @@ describe("ProductVariantService", () => {
|
|||||||
.fn()
|
.fn()
|
||||||
.mockReturnValue(() => Promise.resolve())
|
.mockReturnValue(() => Promise.resolve())
|
||||||
|
|
||||||
productVariantService.setCurrencyPrice = jest
|
|
||||||
.fn()
|
|
||||||
.mockReturnValue(() => Promise.resolve())
|
|
||||||
|
|
||||||
productVariantService.setRegionPrice = jest
|
|
||||||
.fn()
|
|
||||||
.mockReturnValue(() => Promise.resolve())
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("successfully removes obsolete prices and calls setCurrencyPrice on new/existing prices", async () => {
|
|
||||||
await productVariantService.updateVariantPrices("ironman", [
|
|
||||||
{
|
|
||||||
currency_code: "usd",
|
|
||||||
amount: 4000,
|
|
||||||
},
|
|
||||||
])
|
|
||||||
|
|
||||||
expect(
|
|
||||||
moneyAmountRepository.findVariantPricesNotIn
|
|
||||||
).toHaveBeenCalledTimes(1)
|
|
||||||
|
|
||||||
expect(productVariantService.setCurrencyPrice).toHaveBeenCalledTimes(1)
|
|
||||||
expect(productVariantService.setCurrencyPrice).toHaveBeenCalledWith(
|
|
||||||
"ironman",
|
|
||||||
{
|
|
||||||
currency_code: "usd",
|
|
||||||
amount: 4000,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(moneyAmountRepository.remove).toHaveBeenCalledTimes(1)
|
|
||||||
expect(moneyAmountRepository.remove).toHaveBeenCalledWith(oldPrices)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("successfully removes obsolete prices and calls setRegionPrice on new/existing prices", async () => {
|
|
||||||
await productVariantService.updateVariantPrices("ironman", [
|
|
||||||
{
|
|
||||||
region_id: "test-region",
|
|
||||||
amount: 4000,
|
|
||||||
sale_amount: 2000,
|
|
||||||
},
|
|
||||||
])
|
|
||||||
|
|
||||||
expect(
|
|
||||||
moneyAmountRepository.findVariantPricesNotIn
|
|
||||||
).toHaveBeenCalledTimes(1)
|
|
||||||
|
|
||||||
expect(productVariantService.setRegionPrice).toHaveBeenCalledTimes(1)
|
|
||||||
expect(productVariantService.setRegionPrice).toHaveBeenCalledWith(
|
|
||||||
"ironman",
|
|
||||||
{
|
|
||||||
region_id: "test-region",
|
|
||||||
amount: 4000,
|
|
||||||
sale_amount: 2000,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(moneyAmountRepository.remove).toHaveBeenCalledTimes(1)
|
|
||||||
expect(moneyAmountRepository.remove).toHaveBeenCalledWith(oldPrices)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("setCurrencyPrice", () => {
|
|
||||||
const productVariantRepository = MockRepository({
|
|
||||||
findOne: (query) => Promise.resolve({ id: IdMap.getId("ironman") }),
|
|
||||||
})
|
|
||||||
|
|
||||||
const moneyAmountRepository = MockRepository()
|
|
||||||
|
|
||||||
moneyAmountRepository.upsertCurrencyPrice = jest
|
|
||||||
.fn()
|
|
||||||
.mockImplementation((variantId, price) => {
|
|
||||||
return Promise.resolve({
|
|
||||||
id: IdMap.getId("test-amount"),
|
|
||||||
variant_id: IdMap.getId(variantId),
|
|
||||||
...price,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const productVariantService = new ProductVariantService({
|
|
||||||
manager: MockManager,
|
|
||||||
eventBusService,
|
|
||||||
moneyAmountRepository,
|
|
||||||
productVariantRepository,
|
|
||||||
})
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
jest.clearAllMocks()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("calls upsert price with given currency", async () => {
|
|
||||||
await productVariantService.setCurrencyPrice(IdMap.getId("ironman"), {
|
|
||||||
currency_code: "usd",
|
|
||||||
amount: 100,
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(moneyAmountRepository.upsertCurrencyPrice).toHaveBeenCalledTimes(1)
|
|
||||||
expect(moneyAmountRepository.upsertCurrencyPrice).toHaveBeenCalledWith(
|
|
||||||
IdMap.getId("ironman"),
|
|
||||||
{
|
|
||||||
currency_code: "usd",
|
|
||||||
amount: 100,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("getRegionPrice", () => {
|
describe("getRegionPrice", () => {
|
||||||
@@ -590,7 +486,6 @@ describe("ProductVariantService", () => {
|
|||||||
currency_code: "dkk",
|
currency_code: "dkk",
|
||||||
region_id: IdMap.getId("california"),
|
region_id: IdMap.getId("california"),
|
||||||
amount: 1000,
|
amount: 1000,
|
||||||
sale_amount: 750,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
@@ -629,7 +524,8 @@ describe("ProductVariantService", () => {
|
|||||||
IdMap.getId("california")
|
IdMap.getId("california")
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(result).toBe(750)
|
// TODO: Update once PriceStrategy is implemented
|
||||||
|
expect(result).toBe(1000)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("fails if no price is found", async () => {
|
it("fails if no price is found", async () => {
|
||||||
@@ -646,7 +542,7 @@ describe("ProductVariantService", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("setRegionPrice", () => {
|
describe("updateVariantPrices", () => {
|
||||||
const moneyAmountRepository = MockRepository({
|
const moneyAmountRepository = MockRepository({
|
||||||
findOne: (query) => {
|
findOne: (query) => {
|
||||||
if (query.where.region_id === IdMap.getId("cali")) {
|
if (query.where.region_id === IdMap.getId("cali")) {
|
||||||
@@ -657,14 +553,47 @@ describe("ProductVariantService", () => {
|
|||||||
variant_id: IdMap.getId("ironman"),
|
variant_id: IdMap.getId("ironman"),
|
||||||
currency_code: "dkk",
|
currency_code: "dkk",
|
||||||
amount: 750,
|
amount: 750,
|
||||||
sale_amount: 500,
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
create: (p) => p,
|
||||||
|
remove: () => Promise.resolve(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const oldPrices = [
|
||||||
|
{
|
||||||
|
currency_code: "dkk",
|
||||||
|
amount: 1000,
|
||||||
|
variant_id: "ironman",
|
||||||
|
region_id: null,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
moneyAmountRepository.findVariantPricesNotIn = jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation(() => Promise.resolve(oldPrices))
|
||||||
|
|
||||||
|
moneyAmountRepository.upsertVariantCurrencyPrice = jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation(() => Promise.resolve())
|
||||||
|
|
||||||
|
const regionService = {
|
||||||
|
list: jest.fn().mockImplementation((config) => {
|
||||||
|
const idOrIds = config.id
|
||||||
|
|
||||||
|
if (Array.isArray(idOrIds)) {
|
||||||
|
return Promise.resolve(
|
||||||
|
idOrIds.map((id) => ({ id, currency_code: "usd" }))
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return Promise.resolve([{ id: idOrIds, currency_code: "usd" }])
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
const productVariantService = new ProductVariantService({
|
const productVariantService = new ProductVariantService({
|
||||||
manager: MockManager,
|
manager: MockManager,
|
||||||
eventBusService,
|
eventBusService,
|
||||||
|
regionService,
|
||||||
moneyAmountRepository,
|
moneyAmountRepository,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -672,17 +601,43 @@ describe("ProductVariantService", () => {
|
|||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("successfully creates a price if none exist with given region id", async () => {
|
it("successfully removes obsolete prices and calls save on new/existing prices", async () => {
|
||||||
await productVariantService.setRegionPrice(IdMap.getId("ironman"), {
|
await productVariantService.updateVariantPrices("ironman", [
|
||||||
|
{
|
||||||
|
currency_code: "usd",
|
||||||
|
amount: 4000,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(
|
||||||
|
moneyAmountRepository.findVariantPricesNotIn
|
||||||
|
).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
moneyAmountRepository.upsertVariantCurrencyPrice
|
||||||
|
).toHaveBeenCalledTimes(1)
|
||||||
|
expect(
|
||||||
|
moneyAmountRepository.upsertVariantCurrencyPrice
|
||||||
|
).toHaveBeenCalledWith("ironman", {
|
||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
amount: 100,
|
amount: 4000,
|
||||||
region_id: IdMap.getId("cali"),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
expect(moneyAmountRepository.remove).toHaveBeenCalledTimes(1)
|
||||||
|
expect(moneyAmountRepository.remove).toHaveBeenCalledWith(oldPrices)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("successfully creates new a region price", async () => {
|
||||||
|
await productVariantService.updateVariantPrices(IdMap.getId("ironman"), [
|
||||||
|
{
|
||||||
|
amount: 100,
|
||||||
|
region_id: IdMap.getId("cali"),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
expect(moneyAmountRepository.create).toHaveBeenCalledTimes(1)
|
expect(moneyAmountRepository.create).toHaveBeenCalledTimes(1)
|
||||||
expect(moneyAmountRepository.create).toHaveBeenCalledWith({
|
expect(moneyAmountRepository.create).toHaveBeenCalledWith({
|
||||||
variant_id: IdMap.getId("ironman"),
|
variant_id: IdMap.getId("ironman"),
|
||||||
currency_code: "usd",
|
|
||||||
region_id: IdMap.getId("cali"),
|
region_id: IdMap.getId("cali"),
|
||||||
amount: 100,
|
amount: 100,
|
||||||
})
|
})
|
||||||
@@ -690,22 +645,26 @@ describe("ProductVariantService", () => {
|
|||||||
expect(moneyAmountRepository.save).toHaveBeenCalledTimes(1)
|
expect(moneyAmountRepository.save).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("successfully updates a price", async () => {
|
it("successfully creates a currency price", async () => {
|
||||||
await productVariantService.setRegionPrice(IdMap.getId("ironman"), {
|
await productVariantService.updateVariantPrices(IdMap.getId("ironman"), [
|
||||||
currency_code: "dkk",
|
{
|
||||||
amount: 750,
|
id: IdMap.getId("dkk"),
|
||||||
sale_amount: 500,
|
currency_code: "dkk",
|
||||||
})
|
amount: 750,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
expect(moneyAmountRepository.create).toHaveBeenCalledTimes(0)
|
expect(moneyAmountRepository.create).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
expect(moneyAmountRepository.save).toHaveBeenCalledTimes(1)
|
expect(
|
||||||
expect(moneyAmountRepository.save).toHaveBeenCalledWith({
|
moneyAmountRepository.upsertVariantCurrencyPrice
|
||||||
variant_id: IdMap.getId("ironman"),
|
).toHaveBeenCalledTimes(1)
|
||||||
|
expect(
|
||||||
|
moneyAmountRepository.upsertVariantCurrencyPrice
|
||||||
|
).toHaveBeenCalledWith(IdMap.getId("ironman"), {
|
||||||
id: IdMap.getId("dkk"),
|
id: IdMap.getId("dkk"),
|
||||||
currency_code: "dkk",
|
currency_code: "dkk",
|
||||||
amount: 750,
|
amount: 750,
|
||||||
sale_amount: 500,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { BaseService } from "medusa-interfaces"
|
|
||||||
import { Validator, MedusaError } from "medusa-core-utils"
|
|
||||||
import { parse, toSeconds } from "iso8601-duration"
|
import { parse, toSeconds } from "iso8601-duration"
|
||||||
|
import { MedusaError, Validator } from "medusa-core-utils"
|
||||||
|
import { BaseService } from "medusa-interfaces"
|
||||||
import { Brackets, ILike } from "typeorm"
|
import { Brackets, ILike } from "typeorm"
|
||||||
import { formatException } from "../utils/exception-formatter"
|
import { formatException } from "../utils/exception-formatter"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,281 @@
|
|||||||
|
import { MedusaError } from "medusa-core-utils"
|
||||||
|
import { BaseService } from "medusa-interfaces"
|
||||||
|
import { EntityManager } from "typeorm"
|
||||||
|
import { CustomerGroupService } from "."
|
||||||
|
import { CustomerGroup } from "../models/customer-group"
|
||||||
|
import { PriceList } from "../models/price-list"
|
||||||
|
import { MoneyAmountRepository } from "../repositories/money-amount"
|
||||||
|
import { PriceListRepository } from "../repositories/price-list"
|
||||||
|
import { FindConfig } from "../types/common"
|
||||||
|
import {
|
||||||
|
CreatePriceListInput,
|
||||||
|
FilterablePriceListProps,
|
||||||
|
PriceListPriceCreateInput,
|
||||||
|
UpdatePriceListInput,
|
||||||
|
} from "../types/price-list"
|
||||||
|
import { formatException } from "../utils/exception-formatter"
|
||||||
|
|
||||||
|
type PriceListConstructorProps = {
|
||||||
|
manager: EntityManager
|
||||||
|
customerGroupService: CustomerGroupService
|
||||||
|
priceListRepository: typeof PriceListRepository
|
||||||
|
moneyAmountRepository: typeof MoneyAmountRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides layer to manipulate product tags.
|
||||||
|
* @extends BaseService
|
||||||
|
*/
|
||||||
|
class PriceListService extends BaseService {
|
||||||
|
private manager_: EntityManager
|
||||||
|
private customerGroupService_: CustomerGroupService
|
||||||
|
private priceListRepo_: typeof PriceListRepository
|
||||||
|
private moneyAmountRepo_: typeof MoneyAmountRepository
|
||||||
|
|
||||||
|
constructor({
|
||||||
|
manager,
|
||||||
|
customerGroupService,
|
||||||
|
priceListRepository,
|
||||||
|
moneyAmountRepository,
|
||||||
|
}: PriceListConstructorProps) {
|
||||||
|
super()
|
||||||
|
this.manager_ = manager
|
||||||
|
this.customerGroupService_ = customerGroupService
|
||||||
|
this.priceListRepo_ = priceListRepository
|
||||||
|
this.moneyAmountRepo_ = moneyAmountRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
withTransaction(transactionManager: EntityManager): PriceListService {
|
||||||
|
if (!transactionManager) {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
const cloned = new PriceListService({
|
||||||
|
manager: transactionManager,
|
||||||
|
customerGroupService: this.customerGroupService_,
|
||||||
|
priceListRepository: this.priceListRepo_,
|
||||||
|
moneyAmountRepository: this.moneyAmountRepo_,
|
||||||
|
})
|
||||||
|
|
||||||
|
cloned.transactionManager_ = transactionManager
|
||||||
|
|
||||||
|
return cloned
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a product tag by id.
|
||||||
|
* @param {string} priceListId - the id of the product tag to retrieve
|
||||||
|
* @param {Object} config - the config to retrieve the tag by
|
||||||
|
* @return {Promise<PriceList>} the collection.
|
||||||
|
*/
|
||||||
|
async retrieve(
|
||||||
|
priceListId: string,
|
||||||
|
config: FindConfig<PriceList> = {}
|
||||||
|
): Promise<PriceList> {
|
||||||
|
const priceListRepo = this.manager_.getCustomRepository(this.priceListRepo_)
|
||||||
|
|
||||||
|
const query = this.buildQuery_({ id: priceListId }, config)
|
||||||
|
const priceList = await priceListRepo.findOne(query)
|
||||||
|
|
||||||
|
if (!priceList) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`Price list with id: ${priceListId} was not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return priceList
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Price List
|
||||||
|
* @param {CreatePriceListInput} priceListObject - the Price List to create
|
||||||
|
* @return {Promise<PriceList>} created Price List
|
||||||
|
*/
|
||||||
|
async create(priceListObject: CreatePriceListInput): Promise<PriceList> {
|
||||||
|
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||||
|
const priceListRepo = manager.getCustomRepository(this.priceListRepo_)
|
||||||
|
const moneyAmountRepo = manager.getCustomRepository(this.moneyAmountRepo_)
|
||||||
|
|
||||||
|
const { prices, customer_groups, ...rest } = priceListObject
|
||||||
|
|
||||||
|
try {
|
||||||
|
const entity = priceListRepo.create(rest)
|
||||||
|
|
||||||
|
const priceList = await priceListRepo.save(entity)
|
||||||
|
|
||||||
|
if (prices) {
|
||||||
|
await moneyAmountRepo.addPriceListPrices(priceList.id, prices)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (customer_groups) {
|
||||||
|
await this.upsertCustomerGroups_(priceList.id, customer_groups)
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.retrieve(priceList.id, {
|
||||||
|
relations: ["prices", "customer_groups"],
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
throw formatException(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a Price List
|
||||||
|
* @param {string} id - the id of the Product List to update
|
||||||
|
* @param {UpdatePriceListInput} update - the update to apply
|
||||||
|
* @returns {Promise<PriceList>} updated Price List
|
||||||
|
*/
|
||||||
|
async update(id: string, update: UpdatePriceListInput): Promise<PriceList> {
|
||||||
|
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||||
|
const priceListRepo = manager.getCustomRepository(this.priceListRepo_)
|
||||||
|
const moneyAmountRepo = manager.getCustomRepository(this.moneyAmountRepo_)
|
||||||
|
|
||||||
|
const priceList = await this.retrieve(id, { select: ["id"] })
|
||||||
|
|
||||||
|
const { prices, customer_groups, ...rest } = update
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(rest)) {
|
||||||
|
priceList[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
await priceListRepo.save(priceList)
|
||||||
|
|
||||||
|
if (prices) {
|
||||||
|
await moneyAmountRepo.updatePriceListPrices(id, prices)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (customer_groups) {
|
||||||
|
await this.upsertCustomerGroups_(id, customer_groups)
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.retrieve(id, {
|
||||||
|
relations: ["prices", "customer_groups"],
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds prices to a price list in bulk, optionally replacing all existing prices
|
||||||
|
* @param id - id of the price list
|
||||||
|
* @param prices - prices to add
|
||||||
|
* @param replace - whether to replace existing prices
|
||||||
|
* @returns {Promise<PriceList>} updated Price List
|
||||||
|
*/
|
||||||
|
async addPrices(
|
||||||
|
id: string,
|
||||||
|
prices: PriceListPriceCreateInput[],
|
||||||
|
replace = false
|
||||||
|
): Promise<PriceList> {
|
||||||
|
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||||
|
const moneyAmountRepo = manager.getCustomRepository(this.moneyAmountRepo_)
|
||||||
|
|
||||||
|
const priceList = await this.retrieve(id, { select: ["id"] })
|
||||||
|
|
||||||
|
await moneyAmountRepo.addPriceListPrices(priceList.id, prices, replace)
|
||||||
|
|
||||||
|
const result = await this.retrieve(priceList.id, {
|
||||||
|
relations: ["prices"],
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes prices from a price list and deletes the removed prices in bulk
|
||||||
|
* @param id - id of the price list
|
||||||
|
* @param priceIds - ids of the prices to delete
|
||||||
|
* @returns {Promise<void>} updated Price List
|
||||||
|
*/
|
||||||
|
async deletePrices(id: string, priceIds: string[]): Promise<void> {
|
||||||
|
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||||
|
const moneyAmountRepo = manager.getCustomRepository(this.moneyAmountRepo_)
|
||||||
|
|
||||||
|
const priceList = await this.retrieve(id, { select: ["id"] })
|
||||||
|
|
||||||
|
await moneyAmountRepo.deletePriceListPrices(priceList.id, priceIds)
|
||||||
|
|
||||||
|
return Promise.resolve()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a Price List
|
||||||
|
* Will never fail due to delete being idempotent.
|
||||||
|
* @param id - id of the price list
|
||||||
|
* @returns {Promise<void>} empty promise
|
||||||
|
*/
|
||||||
|
async delete(id: string): Promise<void> {
|
||||||
|
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||||
|
const priceListRepo = manager.getCustomRepository(this.priceListRepo_)
|
||||||
|
|
||||||
|
const priceList = await priceListRepo.findOne({ where: { id: id } })
|
||||||
|
if (!priceList) {
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
|
||||||
|
await priceListRepo.delete(priceList)
|
||||||
|
|
||||||
|
return Promise.resolve()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists Price Lists
|
||||||
|
* @param {Object} selector - the query object for find
|
||||||
|
* @param {Object} config - the config to be used for find
|
||||||
|
* @return {Promise<PriceList[]>} the result of the find operation
|
||||||
|
*/
|
||||||
|
async list(
|
||||||
|
selector: FilterablePriceListProps = {},
|
||||||
|
config: FindConfig<PriceList> = { skip: 0, take: 20 }
|
||||||
|
): Promise<PriceList[]> {
|
||||||
|
const priceListRepo = this.manager_.getCustomRepository(this.priceListRepo_)
|
||||||
|
|
||||||
|
const query = this.buildQuery_(selector, config)
|
||||||
|
return await priceListRepo.find(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists Price Lists and adds count
|
||||||
|
* @param {Object} selector - the query object for find
|
||||||
|
* @param {Object} config - the config to be used for find
|
||||||
|
* @return {Promise} the result of the find operation
|
||||||
|
*/
|
||||||
|
async listAndCount(
|
||||||
|
selector: FilterablePriceListProps = {},
|
||||||
|
config: FindConfig<PriceList> = { skip: 0, take: 20 }
|
||||||
|
): Promise<[PriceList[], number]> {
|
||||||
|
const priceListRepo = this.manager_.getCustomRepository(this.priceListRepo_)
|
||||||
|
|
||||||
|
const query = this.buildQuery_(selector, config)
|
||||||
|
return await priceListRepo.findAndCount(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
async upsertCustomerGroups_(
|
||||||
|
priceListId: string,
|
||||||
|
customerGroups: { id: string }[]
|
||||||
|
): Promise<void> {
|
||||||
|
const priceListRepo = this.manager_.getCustomRepository(this.priceListRepo_)
|
||||||
|
const priceList = await this.retrieve(priceListId, { select: ["id"] })
|
||||||
|
|
||||||
|
const groups: CustomerGroup[] = []
|
||||||
|
|
||||||
|
for (const cg of customerGroups) {
|
||||||
|
const customerGroup = await this.customerGroupService_.retrieve(cg.id)
|
||||||
|
groups.push(customerGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
priceList.customer_groups = groups
|
||||||
|
|
||||||
|
await priceListRepo.save(priceList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PriceListService
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
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, In, SelectQueryBuilder } from "typeorm"
|
import { Brackets, EntityManager, ILike, SelectQueryBuilder } from "typeorm"
|
||||||
import { MoneyAmount } from "../models/money-amount"
|
import { MoneyAmount } from ".."
|
||||||
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"
|
||||||
@@ -230,7 +230,6 @@ class ProductVariantService extends BaseService {
|
|||||||
await this.setRegionPrice(result.id, {
|
await this.setRegionPrice(result.id, {
|
||||||
amount: price.amount,
|
amount: price.amount,
|
||||||
region_id: price.region_id,
|
region_id: price.region_id,
|
||||||
sale_amount: price.sale_amount,
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
await this.setCurrencyPrice(result.id, price)
|
await this.setCurrencyPrice(result.id, price)
|
||||||
@@ -317,6 +316,13 @@ class ProductVariantService extends BaseService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a variant's prices.
|
||||||
|
* Deletes any prices that are not in the update object, and is not associated with a price list.
|
||||||
|
* @param variantId - the id of variant variant
|
||||||
|
* @param prices - the update prices
|
||||||
|
* @returns {Promise<void>} empty promise
|
||||||
|
*/
|
||||||
async updateVariantPrices(
|
async updateVariantPrices(
|
||||||
variantId: string,
|
variantId: string,
|
||||||
prices: ProductVariantPrice[]
|
prices: ProductVariantPrice[]
|
||||||
@@ -337,7 +343,6 @@ class ProductVariantService extends BaseService {
|
|||||||
await this.setRegionPrice(variantId, {
|
await this.setRegionPrice(variantId, {
|
||||||
region_id: price.region_id,
|
region_id: price.region_id,
|
||||||
amount: price.amount,
|
amount: price.amount,
|
||||||
sale_amount: price.sale_amount || undefined,
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
await this.setCurrencyPrice(variantId, price)
|
await this.setCurrencyPrice(variantId, price)
|
||||||
@@ -348,25 +353,6 @@ class ProductVariantService extends BaseService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the default price for the given currency.
|
|
||||||
* @param {string} variantId - the id of the variant to set prices for
|
|
||||||
* @param {ProductVariantPrice} price - the price for the variant
|
|
||||||
* @return {Promise} the result of the update operation
|
|
||||||
*/
|
|
||||||
async setCurrencyPrice(
|
|
||||||
variantId: string,
|
|
||||||
price: ProductVariantPrice
|
|
||||||
): Promise<MoneyAmount> {
|
|
||||||
return this.atomicPhase_(async (manager: EntityManager) => {
|
|
||||||
const moneyAmountRepo = manager.getCustomRepository(
|
|
||||||
this.moneyAmountRepository_
|
|
||||||
)
|
|
||||||
|
|
||||||
return await moneyAmountRepo.upsertCurrencyPrice(variantId, price)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the price specific to a region. If no region specific money amount
|
* Gets the price specific to a region. If no region specific money amount
|
||||||
* 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
|
||||||
@@ -406,17 +392,12 @@ class ProductVariantService extends BaseService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always return sale price, if present
|
|
||||||
if (moneyAmount.sale_amount) {
|
|
||||||
return moneyAmount.sale_amount
|
|
||||||
}
|
|
||||||
|
|
||||||
return moneyAmount.amount
|
return moneyAmount.amount
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the price of a specific region
|
* Sets the default price of a specific region
|
||||||
* @param {string} variantId - the id of the variant to update
|
* @param {string} variantId - the id of the variant to update
|
||||||
* @param {string} price - the price for the variant.
|
* @param {string} price - the price for the variant.
|
||||||
* @return {Promise} the result of the update operation
|
* @return {Promise} the result of the update operation
|
||||||
@@ -434,6 +415,7 @@ class ProductVariantService extends BaseService {
|
|||||||
where: {
|
where: {
|
||||||
variant_id: variantId,
|
variant_id: variantId,
|
||||||
region_id: price.region_id,
|
region_id: price.region_id,
|
||||||
|
price_list_id: null,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -444,7 +426,6 @@ class ProductVariantService extends BaseService {
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
moneyAmount.amount = price.amount
|
moneyAmount.amount = price.amount
|
||||||
moneyAmount.sale_amount = price.sale_amount
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await moneyAmountRepo.save(moneyAmount)
|
const result = await moneyAmountRepo.save(moneyAmount)
|
||||||
@@ -452,6 +433,25 @@ class ProductVariantService extends BaseService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the default price for the given currency.
|
||||||
|
* @param {string} variantId - the id of the variant to set prices for
|
||||||
|
* @param {ProductVariantPrice} price - the price for the variant
|
||||||
|
* @return {Promise} the result of the update operation
|
||||||
|
*/
|
||||||
|
async setCurrencyPrice(
|
||||||
|
variantId: string,
|
||||||
|
price: ProductVariantPrice
|
||||||
|
): Promise<MoneyAmount> {
|
||||||
|
return this.atomicPhase_(async (manager: EntityManager) => {
|
||||||
|
const moneyAmountRepo = manager.getCustomRepository(
|
||||||
|
this.moneyAmountRepository_
|
||||||
|
)
|
||||||
|
|
||||||
|
return await moneyAmountRepo.upsertVariantCurrencyPrice(variantId, price)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates variant's option value.
|
* Updates variant's option value.
|
||||||
* Option value must be of type string or number.
|
* Option value must be of type string or number.
|
||||||
@@ -636,7 +636,10 @@ class ProductVariantService extends BaseService {
|
|||||||
this.productVariantRepository_
|
this.productVariantRepository_
|
||||||
)
|
)
|
||||||
|
|
||||||
const variant = await variantRepo.findOne({ where: { id: variantId } })
|
const variant = await variantRepo.findOne({
|
||||||
|
where: { id: variantId },
|
||||||
|
relations: ["prices"],
|
||||||
|
})
|
||||||
|
|
||||||
if (!variant) {
|
if (!variant) {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
@@ -662,7 +665,10 @@ class ProductVariantService extends BaseService {
|
|||||||
* @param {Object} metadata - the metadata to set
|
* @param {Object} metadata - the metadata to set
|
||||||
* @return {Object} updated metadata object
|
* @return {Object} updated metadata object
|
||||||
*/
|
*/
|
||||||
setMetadata_(variant: ProductVariant, metadata: object): Record<string, any> {
|
setMetadata_(
|
||||||
|
variant: ProductVariant,
|
||||||
|
metadata: object
|
||||||
|
): Record<string, unknown> {
|
||||||
const existing = variant.metadata || {}
|
const existing = variant.metadata || {}
|
||||||
const newData = {}
|
const newData = {}
|
||||||
for (const [key, value] of Object.entries(metadata)) {
|
for (const [key, value] of Object.entries(metadata)) {
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
import { Type } from "class-transformer"
|
||||||
|
import {
|
||||||
|
IsEnum,
|
||||||
|
IsInt,
|
||||||
|
IsOptional,
|
||||||
|
IsString,
|
||||||
|
Validate,
|
||||||
|
ValidateIf,
|
||||||
|
ValidateNested,
|
||||||
|
} from "class-validator"
|
||||||
|
import { PriceList } from "../models/price-list"
|
||||||
|
import { DateComparisonOperator } from "./common"
|
||||||
|
import { XorConstraint } from "./validators/xor"
|
||||||
|
|
||||||
|
export enum PriceListType {
|
||||||
|
SALE = "sale",
|
||||||
|
OVERRIDE = "override",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum PriceListStatus {
|
||||||
|
ACTIVE = "active",
|
||||||
|
DRAFT = "draft",
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FilterablePriceListProps {
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
id?: string
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
q?: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsEnum(PriceListStatus, { each: true })
|
||||||
|
status?: PriceListStatus[]
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
name?: string
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
description?: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsEnum(PriceListType, { each: true })
|
||||||
|
type?: PriceListType[]
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => DateComparisonOperator)
|
||||||
|
created_at?: DateComparisonOperator
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => DateComparisonOperator)
|
||||||
|
updated_at?: DateComparisonOperator
|
||||||
|
|
||||||
|
@ValidateNested()
|
||||||
|
@IsOptional()
|
||||||
|
@Type(() => DateComparisonOperator)
|
||||||
|
deleted_at?: DateComparisonOperator
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AdminPriceListPricesUpdateReq {
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
id?: string
|
||||||
|
|
||||||
|
@ValidateIf((o) => !o.id)
|
||||||
|
@Validate(XorConstraint, ["currency_code"])
|
||||||
|
region_id?: string
|
||||||
|
|
||||||
|
@ValidateIf((o) => !o.id)
|
||||||
|
@Validate(XorConstraint, ["region_id"])
|
||||||
|
currency_code?: string
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
variant_id: string
|
||||||
|
|
||||||
|
@IsInt()
|
||||||
|
amount: number
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsInt()
|
||||||
|
min_quantity?: number
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsInt()
|
||||||
|
max_quantity?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AdminPriceListPricesCreateReq {
|
||||||
|
@Validate(XorConstraint, ["currency_code"])
|
||||||
|
region_id?: string
|
||||||
|
|
||||||
|
@Validate(XorConstraint, ["region_id"])
|
||||||
|
currency_code?: string
|
||||||
|
|
||||||
|
@IsInt()
|
||||||
|
amount: number
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
variant_id: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsInt()
|
||||||
|
min_quantity?: number
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsInt()
|
||||||
|
max_quantity?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CreatePriceListInput = {
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
type: PriceListType
|
||||||
|
status?: PriceListStatus
|
||||||
|
prices: AdminPriceListPricesCreateReq[]
|
||||||
|
customer_groups?: { id: string }[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UpdatePriceListInput = Partial<
|
||||||
|
Pick<
|
||||||
|
PriceList,
|
||||||
|
"name" | "description" | "starts_at" | "ends_at" | "status" | "type"
|
||||||
|
>
|
||||||
|
> & {
|
||||||
|
prices?: AdminPriceListPricesUpdateReq[]
|
||||||
|
customer_groups?: { id: string }[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PriceListPriceUpdateInput = {
|
||||||
|
id?: string
|
||||||
|
variant_id?: string
|
||||||
|
region_id?: string
|
||||||
|
currency_code?: string
|
||||||
|
amount?: number
|
||||||
|
min_quantity?: number
|
||||||
|
max_quantity?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PriceListPriceCreateInput = {
|
||||||
|
region_id?: string
|
||||||
|
currency_code?: string
|
||||||
|
amount: number
|
||||||
|
min_quantity?: number
|
||||||
|
max_quantity?: number
|
||||||
|
}
|
||||||
@@ -1,16 +1,28 @@
|
|||||||
import { IsBoolean, IsNumber, IsString, ValidateNested } from "class-validator"
|
import {
|
||||||
|
IsBoolean,
|
||||||
|
IsInt,
|
||||||
|
IsNumber,
|
||||||
|
IsOptional,
|
||||||
|
IsString,
|
||||||
|
Validate,
|
||||||
|
ValidateIf,
|
||||||
|
ValidateNested,
|
||||||
|
} from "class-validator"
|
||||||
import { IsType } from "../utils/validators/is-type"
|
import { IsType } from "../utils/validators/is-type"
|
||||||
import {
|
import {
|
||||||
DateComparisonOperator,
|
DateComparisonOperator,
|
||||||
NumericalComparisonOperator,
|
NumericalComparisonOperator,
|
||||||
StringComparisonOperator,
|
StringComparisonOperator,
|
||||||
} from "./common"
|
} from "./common"
|
||||||
|
import { XorConstraint } from "./validators/xor"
|
||||||
|
|
||||||
export type ProductVariantPrice = {
|
export type ProductVariantPrice = {
|
||||||
|
id?: string
|
||||||
currency_code?: string
|
currency_code?: string
|
||||||
region_id?: string
|
region_id?: string
|
||||||
amount: number
|
amount: number
|
||||||
sale_amount?: number | undefined
|
min_quantity?: number
|
||||||
|
max_quantity?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProductVariantOption = {
|
export type ProductVariantOption = {
|
||||||
@@ -130,3 +142,47 @@ export class FilterableProductVariantProps {
|
|||||||
@IsType([DateComparisonOperator])
|
@IsType([DateComparisonOperator])
|
||||||
updated_at?: DateComparisonOperator
|
updated_at?: DateComparisonOperator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ProductVariantPricesUpdateReq {
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
id?: string
|
||||||
|
|
||||||
|
@ValidateIf((o) => !o.id)
|
||||||
|
@Validate(XorConstraint, ["currency_code"])
|
||||||
|
region_id?: string
|
||||||
|
|
||||||
|
@ValidateIf((o) => !o.id)
|
||||||
|
@Validate(XorConstraint, ["region_id"])
|
||||||
|
currency_code?: string
|
||||||
|
|
||||||
|
@IsInt()
|
||||||
|
amount: number
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsInt()
|
||||||
|
min_quantity?: number
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsInt()
|
||||||
|
max_quantity?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ProductVariantPricesCreateReq {
|
||||||
|
@Validate(XorConstraint, ["currency_code"])
|
||||||
|
region_id?: string
|
||||||
|
|
||||||
|
@Validate(XorConstraint, ["region_id"])
|
||||||
|
currency_code?: string
|
||||||
|
|
||||||
|
@IsInt()
|
||||||
|
amount: number
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsInt()
|
||||||
|
min_quantity?: number
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsInt()
|
||||||
|
max_quantity?: number
|
||||||
|
}
|
||||||
Reference in new issue
Block a user