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:
co-authored by
Sebastian Rindom
Philip Korsholm
parent
23f8399c16
commit
5300926db8
@@ -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",
|
||||
"deleted_at": null,
|
||||
"id": StringMatching /\\^test-price\\*/,
|
||||
"max_quantity": null,
|
||||
"min_quantity": null,
|
||||
"price_list_id": null,
|
||||
"region_id": null,
|
||||
"sale_amount": null,
|
||||
"updated_at": Any<String>,
|
||||
"variant_id": StringMatching /\\^test-variant\\*/,
|
||||
},
|
||||
@@ -159,8 +161,10 @@ Array [
|
||||
"currency_code": "usd",
|
||||
"deleted_at": null,
|
||||
"id": StringMatching /\\^test-price\\*/,
|
||||
"max_quantity": null,
|
||||
"min_quantity": null,
|
||||
"price_list_id": null,
|
||||
"region_id": null,
|
||||
"sale_amount": null,
|
||||
"updated_at": Any<String>,
|
||||
"variant_id": StringMatching /\\^test-variant\\*/,
|
||||
},
|
||||
@@ -208,8 +212,10 @@ Array [
|
||||
"currency_code": "usd",
|
||||
"deleted_at": null,
|
||||
"id": StringMatching /\\^test-price\\*/,
|
||||
"max_quantity": null,
|
||||
"min_quantity": null,
|
||||
"price_list_id": null,
|
||||
"region_id": null,
|
||||
"sale_amount": null,
|
||||
"updated_at": Any<String>,
|
||||
"variant_id": StringMatching /\\^test-variant\\*/,
|
||||
},
|
||||
@@ -315,8 +321,10 @@ Array [
|
||||
"currency_code": "usd",
|
||||
"deleted_at": null,
|
||||
"id": StringMatching /\\^test-price\\*/,
|
||||
"max_quantity": null,
|
||||
"min_quantity": null,
|
||||
"price_list_id": null,
|
||||
"region_id": null,
|
||||
"sale_amount": null,
|
||||
"updated_at": Any<String>,
|
||||
"variant_id": StringMatching /\\^test-variant\\*/,
|
||||
},
|
||||
@@ -364,8 +372,10 @@ Array [
|
||||
"currency_code": "usd",
|
||||
"deleted_at": null,
|
||||
"id": StringMatching /\\^test-price\\*/,
|
||||
"max_quantity": null,
|
||||
"min_quantity": null,
|
||||
"price_list_id": null,
|
||||
"region_id": "test-region",
|
||||
"sale_amount": null,
|
||||
"updated_at": Any<String>,
|
||||
"variant_id": StringMatching /\\^test-variant\\*/,
|
||||
},
|
||||
@@ -561,8 +571,10 @@ Array [
|
||||
"currency_code": "usd",
|
||||
"deleted_at": null,
|
||||
"id": Any<String>,
|
||||
"max_quantity": null,
|
||||
"min_quantity": null,
|
||||
"price_list_id": null,
|
||||
"region_id": null,
|
||||
"sale_amount": null,
|
||||
"updated_at": Any<String>,
|
||||
"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 productSeeder = require("../../helpers/product-seeder")
|
||||
const { ProductVariant } = require("@medusajs/medusa")
|
||||
const priceListSeeder = require("../../helpers/price-list-seeder")
|
||||
|
||||
jest.setTimeout(50000)
|
||||
|
||||
@@ -773,7 +774,20 @@ describe("/admin/products", () => {
|
||||
{
|
||||
title: "Test variant",
|
||||
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" }],
|
||||
},
|
||||
],
|
||||
@@ -790,66 +804,126 @@ describe("/admin/products", () => {
|
||||
})
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.product).toEqual(
|
||||
expect.objectContaining({
|
||||
title: "Test",
|
||||
discountable: true,
|
||||
is_giftcard: false,
|
||||
handle: "test",
|
||||
status: "draft",
|
||||
images: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
url: "test-image.png",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
url: "test-image-2.png",
|
||||
}),
|
||||
]),
|
||||
thumbnail: "test-image.png",
|
||||
tags: [
|
||||
expect.objectContaining({
|
||||
value: "123",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
value: "456",
|
||||
}),
|
||||
],
|
||||
type: expect.objectContaining({
|
||||
value: "test-type",
|
||||
}),
|
||||
collection: expect.objectContaining({
|
||||
id: "test-collection",
|
||||
title: "Test collection",
|
||||
}),
|
||||
options: [
|
||||
expect.objectContaining({
|
||||
title: "size",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
title: "color",
|
||||
}),
|
||||
],
|
||||
variants: [
|
||||
expect.objectContaining({
|
||||
title: "Test variant",
|
||||
prices: [
|
||||
expect.objectContaining({
|
||||
currency_code: "usd",
|
||||
amount: 100,
|
||||
}),
|
||||
],
|
||||
options: [
|
||||
expect.objectContaining({
|
||||
value: "large",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
value: "green",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
expect(response.data.product).toMatchSnapshot({
|
||||
id: expect.stringMatching(/^prod_*/),
|
||||
title: "Test",
|
||||
discountable: true,
|
||||
is_giftcard: false,
|
||||
handle: "test",
|
||||
status: "draft",
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
profile_id: expect.stringMatching(/^sp_*/),
|
||||
images: [
|
||||
{
|
||||
id: expect.any(String),
|
||||
url: "test-image.png",
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
},
|
||||
{
|
||||
id: expect.any(String),
|
||||
url: "test-image-2.png",
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
},
|
||||
],
|
||||
thumbnail: "test-image.png",
|
||||
tags: [
|
||||
{
|
||||
id: expect.any(String),
|
||||
value: "123",
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
},
|
||||
{
|
||||
id: expect.any(String),
|
||||
value: "456",
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
},
|
||||
],
|
||||
type: {
|
||||
value: "test-type",
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
},
|
||||
collection: {
|
||||
id: "test-collection",
|
||||
title: "Test collection",
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
},
|
||||
options: [
|
||||
{
|
||||
id: expect.stringMatching(/^opt_*/),
|
||||
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 () => {
|
||||
@@ -1006,8 +1080,7 @@ describe("/admin/products", () => {
|
||||
prices: [
|
||||
{
|
||||
currency_code: "usd",
|
||||
amount: 100,
|
||||
sale_amount: 75,
|
||||
amount: 75,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1030,36 +1103,92 @@ describe("/admin/products", () => {
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
|
||||
expect(response.data.product).toEqual(
|
||||
expect.objectContaining({
|
||||
images: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
url: "test-image-2.png",
|
||||
}),
|
||||
]),
|
||||
thumbnail: "test-image-2.png",
|
||||
tags: [
|
||||
expect.objectContaining({
|
||||
value: "123",
|
||||
}),
|
||||
],
|
||||
variants: [
|
||||
expect.objectContaining({
|
||||
prices: [
|
||||
expect.objectContaining({
|
||||
sale_amount: 75,
|
||||
amount: 100,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
status: "published",
|
||||
collection: null,
|
||||
type: expect.objectContaining({
|
||||
value: "test-type-2",
|
||||
}),
|
||||
})
|
||||
)
|
||||
expect(response.data.product).toMatchSnapshot({
|
||||
id: "test-product",
|
||||
created_at: expect.any(String),
|
||||
description: "test-product-description",
|
||||
discountable: true,
|
||||
handle: "test-product",
|
||||
images: [
|
||||
{
|
||||
created_at: expect.any(String),
|
||||
deleted_at: null,
|
||||
id: expect.stringMatching(/^img_*/),
|
||||
metadata: null,
|
||||
updated_at: expect.any(String),
|
||||
url: "test-image-2.png",
|
||||
},
|
||||
],
|
||||
is_giftcard: false,
|
||||
options: [
|
||||
{
|
||||
created_at: expect.any(String),
|
||||
id: "test-option",
|
||||
product_id: "test-product",
|
||||
title: "test-option",
|
||||
updated_at: expect.any(String),
|
||||
},
|
||||
],
|
||||
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 () => {
|
||||
@@ -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 () => {
|
||||
try {
|
||||
await productSeeder(dbConnection)
|
||||
await adminSeeder(dbConnection)
|
||||
await priceListSeeder(dbConnection)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
throw err
|
||||
@@ -1205,7 +1335,7 @@ describe("/admin/products", () => {
|
||||
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 data = {
|
||||
prices: [
|
||||
@@ -1239,6 +1369,33 @@ describe("/admin/products", () => {
|
||||
amount: 1500,
|
||||
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.data).toEqual({
|
||||
product: expect.objectContaining({
|
||||
id: "test-product",
|
||||
variants: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: "test-variant",
|
||||
prices: [
|
||||
expect.objectContaining({
|
||||
amount: 100,
|
||||
currency_code: "usd",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: 4500,
|
||||
currency_code: "eur",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
})
|
||||
expect(response.data).toEqual(
|
||||
expect.objectContaining({
|
||||
product: expect.objectContaining({
|
||||
id: "test-product",
|
||||
variants: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: "test-variant",
|
||||
prices: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
amount: 100,
|
||||
currency_code: "usd",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
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 () => {
|
||||
@@ -1343,7 +1529,7 @@ describe("/admin/products", () => {
|
||||
const data = {
|
||||
prices: [
|
||||
{
|
||||
currency_code: "eur",
|
||||
currency_code: "usd",
|
||||
amount: 4500,
|
||||
},
|
||||
],
|
||||
@@ -1362,14 +1548,43 @@ describe("/admin/products", () => {
|
||||
expect(response.status).toEqual(200)
|
||||
|
||||
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 () => {
|
||||
@@ -1400,18 +1615,47 @@ describe("/admin/products", () => {
|
||||
expect(response.status).toEqual(200)
|
||||
|
||||
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 () => {
|
||||
|
||||
@@ -32,8 +32,8 @@ describe("/admin/store", () => {
|
||||
|
||||
afterEach(async () => {
|
||||
const db = useDb()
|
||||
db.teardown()
|
||||
medusaProcess.kill()
|
||||
await db.teardown()
|
||||
await medusaProcess.kill()
|
||||
})
|
||||
|
||||
it("has created store with default currency", async () => {
|
||||
|
||||
@@ -37,8 +37,10 @@ Object {
|
||||
"currency_code": "usd",
|
||||
"deleted_at": null,
|
||||
"id": "test-price",
|
||||
"max_quantity": null,
|
||||
"min_quantity": null,
|
||||
"price_list_id": null,
|
||||
"region_id": null,
|
||||
"sale_amount": null,
|
||||
"updated_at": Any<String>,
|
||||
"variant_id": "test-variant",
|
||||
},
|
||||
@@ -93,8 +95,10 @@ Object {
|
||||
"currency_code": "usd",
|
||||
"deleted_at": null,
|
||||
"id": "test-price",
|
||||
"max_quantity": null,
|
||||
"min_quantity": null,
|
||||
"price_list_id": null,
|
||||
"region_id": null,
|
||||
"sale_amount": null,
|
||||
"updated_at": Any<String>,
|
||||
"variant_id": "test-variant",
|
||||
},
|
||||
@@ -150,8 +154,10 @@ Object {
|
||||
"currency_code": "usd",
|
||||
"deleted_at": null,
|
||||
"id": Any<String>,
|
||||
"max_quantity": null,
|
||||
"min_quantity": null,
|
||||
"price_list_id": null,
|
||||
"region_id": null,
|
||||
"sale_amount": null,
|
||||
"updated_at": Any<String>,
|
||||
"variant_id": Any<String>,
|
||||
},
|
||||
|
||||
@@ -162,8 +162,10 @@ Object {
|
||||
"currency_code": "usd",
|
||||
"deleted_at": null,
|
||||
"id": "test-price",
|
||||
"max_quantity": null,
|
||||
"min_quantity": null,
|
||||
"price_list_id": null,
|
||||
"region_id": null,
|
||||
"sale_amount": null,
|
||||
"updated_at": Any<String>,
|
||||
"variant_id": "test-variant",
|
||||
},
|
||||
@@ -211,8 +213,10 @@ Object {
|
||||
"currency_code": "usd",
|
||||
"deleted_at": null,
|
||||
"id": "test-price2",
|
||||
"max_quantity": null,
|
||||
"min_quantity": null,
|
||||
"price_list_id": null,
|
||||
"region_id": null,
|
||||
"sale_amount": null,
|
||||
"updated_at": Any<String>,
|
||||
"variant_id": "test-variant_2",
|
||||
},
|
||||
@@ -260,8 +264,10 @@ Object {
|
||||
"currency_code": "usd",
|
||||
"deleted_at": null,
|
||||
"id": "test-price1",
|
||||
"max_quantity": null,
|
||||
"min_quantity": null,
|
||||
"price_list_id": null,
|
||||
"region_id": null,
|
||||
"sale_amount": null,
|
||||
"updated_at": Any<String>,
|
||||
"variant_id": "test-variant_1",
|
||||
},
|
||||
@@ -314,8 +320,10 @@ Object {
|
||||
"currency_code": "usd",
|
||||
"deleted_at": null,
|
||||
"id": Any<String>,
|
||||
"max_quantity": null,
|
||||
"min_quantity": null,
|
||||
"price_list_id": null,
|
||||
"region_id": null,
|
||||
"sale_amount": null,
|
||||
"updated_at": Any<String>,
|
||||
"variant_id": Any<String>,
|
||||
},
|
||||
|
||||
@@ -99,7 +99,9 @@ describe("/store/variants", () => {
|
||||
deleted_at: null,
|
||||
id: "test-price",
|
||||
region_id: null,
|
||||
sale_amount: null,
|
||||
min_quantity: null,
|
||||
max_quantity: null,
|
||||
price_list_id: null,
|
||||
variant_id: "test-variant",
|
||||
},
|
||||
],
|
||||
@@ -193,7 +195,9 @@ describe("/store/variants", () => {
|
||||
deleted_at: null,
|
||||
id: "test-price",
|
||||
region_id: null,
|
||||
sale_amount: null,
|
||||
min_quantity: null,
|
||||
max_quantity: null,
|
||||
price_list_id: null,
|
||||
variant_id: "test-variant",
|
||||
},
|
||||
],
|
||||
|
||||
@@ -353,7 +353,9 @@ describe("/store/products", () => {
|
||||
deleted_at: null,
|
||||
id: "test-price",
|
||||
region_id: null,
|
||||
sale_amount: null,
|
||||
min_quantity: null,
|
||||
max_quantity: null,
|
||||
price_list_id: null,
|
||||
updated_at: expect.any(String),
|
||||
variant_id: "test-variant",
|
||||
},
|
||||
@@ -396,7 +398,9 @@ describe("/store/products", () => {
|
||||
deleted_at: null,
|
||||
id: "test-price2",
|
||||
region_id: null,
|
||||
sale_amount: null,
|
||||
min_quantity: null,
|
||||
max_quantity: null,
|
||||
price_list_id: null,
|
||||
variant_id: "test-variant_2",
|
||||
},
|
||||
],
|
||||
@@ -438,7 +442,9 @@ describe("/store/products", () => {
|
||||
deleted_at: null,
|
||||
id: "test-price1",
|
||||
region_id: null,
|
||||
sale_amount: null,
|
||||
min_quantity: null,
|
||||
max_quantity: null,
|
||||
price_list_id: null,
|
||||
updated_at: expect.any(String),
|
||||
variant_id: "test-variant_1",
|
||||
},
|
||||
|
||||
@@ -36,17 +36,17 @@ module.exports = async (connection, data = {}) => {
|
||||
has_account: true,
|
||||
})
|
||||
|
||||
const customer5 = manager.create(Customer, {
|
||||
const customer5 = await manager.create(Customer, {
|
||||
id: "test-customer-5",
|
||||
email: "test5@email.com",
|
||||
})
|
||||
|
||||
const customer6 = manager.create(Customer, {
|
||||
const customer6 = await manager.create(Customer, {
|
||||
id: "test-customer-6",
|
||||
email: "test6@email.com",
|
||||
})
|
||||
|
||||
const customer7 = manager.create(Customer, {
|
||||
const customer7 = await manager.create(Customer, {
|
||||
id: "test-customer-7",
|
||||
email: "test7@email.com",
|
||||
})
|
||||
@@ -78,13 +78,13 @@ module.exports = async (connection, data = {}) => {
|
||||
name: "test-group-4",
|
||||
})
|
||||
|
||||
const c_group_5 = manager.create(CustomerGroup, {
|
||||
const c_group_5 = await manager.create(CustomerGroup, {
|
||||
id: "test-group-5",
|
||||
name: "test-group-5",
|
||||
})
|
||||
await manager.save(c_group_5)
|
||||
|
||||
const c_group_6 = manager.create(CustomerGroup, {
|
||||
const c_group_6 = await manager.create(CustomerGroup, {
|
||||
id: "test-group-6",
|
||||
name: "test-group-6",
|
||||
})
|
||||
@@ -99,7 +99,7 @@ module.exports = async (connection, data = {}) => {
|
||||
customer7.groups = [c_group_5, c_group_6]
|
||||
await manager.save(customer7)
|
||||
|
||||
const c_group_delete = manager.create(CustomerGroup, {
|
||||
const c_group_delete = await manager.create(CustomerGroup, {
|
||||
id: "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\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@medusajs/medusa": "1.2.0-dev-1646213107704",
|
||||
"@medusajs/medusa": "1.2.0-dev-1647336201011",
|
||||
"faker": "^5.5.3",
|
||||
"medusa-interfaces": "1.2.0-dev-1646213107704",
|
||||
"medusa-interfaces": "1.2.0-dev-1647336201011",
|
||||
"typeorm": "^0.2.31"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.12.10",
|
||||
"@babel/core": "^7.12.10",
|
||||
"@babel/node": "^7.12.10",
|
||||
"babel-preset-medusa-package": "1.1.19-dev-1646213107704",
|
||||
"babel-preset-medusa-package": "1.1.19-dev-1647336201011",
|
||||
"jest": "^26.6.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1256,10 +1256,10 @@
|
||||
"@types/yargs" "^15.0.0"
|
||||
chalk "^4.0.0"
|
||||
|
||||
"@medusajs/medusa-cli@1.2.0-dev-1646213107704":
|
||||
version "1.2.0-dev-1646213107704"
|
||||
resolved "http://localhost:4873/@medusajs%2fmedusa-cli/-/medusa-cli-1.2.0-dev-1646213107704.tgz#b84f9143450f3c03e732277b6c5bbeb0df761b2e"
|
||||
integrity sha512-U1BqPe167vxpzf2YfopVpNTq0qcBkX3jkWFCNduHEjkBbuOXchlB/MheKtxIsxjHV1hzmjTr9uw3owu93pDYCQ==
|
||||
"@medusajs/medusa-cli@1.2.0-dev-1647336201011":
|
||||
version "1.2.0-dev-1647336201011"
|
||||
resolved "http://localhost:4873/@medusajs%2fmedusa-cli/-/medusa-cli-1.2.0-dev-1647336201011.tgz#9842f8d2f53d5a7d2207ec23d7d4b18a33e65c54"
|
||||
integrity sha512-wVqcWMh0+8ko/Db66GDrp68XpaVkvPrjrc4mn7KdfD9DnIgFCOoFakuYsQUxHDNak9EA96idZSF8/hsHALlOqg==
|
||||
dependencies:
|
||||
"@babel/polyfill" "^7.8.7"
|
||||
"@babel/runtime" "^7.9.6"
|
||||
@@ -1277,8 +1277,8 @@
|
||||
is-valid-path "^0.1.1"
|
||||
joi-objectid "^3.0.1"
|
||||
meant "^1.0.1"
|
||||
medusa-core-utils "1.1.31-dev-1646213107704"
|
||||
medusa-telemetry "0.0.11-dev-1646213107704"
|
||||
medusa-core-utils "1.1.31-dev-1647336201011"
|
||||
medusa-telemetry "0.0.11-dev-1647336201011"
|
||||
netrc-parser "^3.1.6"
|
||||
open "^8.0.6"
|
||||
ora "^5.4.1"
|
||||
@@ -1292,13 +1292,13 @@
|
||||
winston "^3.3.3"
|
||||
yargs "^15.3.1"
|
||||
|
||||
"@medusajs/medusa@1.2.0-dev-1646213107704":
|
||||
version "1.2.0-dev-1646213107704"
|
||||
resolved "http://localhost:4873/@medusajs%2fmedusa/-/medusa-1.2.0-dev-1646213107704.tgz#40ed0efe4f8e242c620eb1a984f0904465576920"
|
||||
integrity sha512-pZnoM/U6WF4IdKriX7lKaxlmt9nlK4WoQ4PtzLjvZemjNHKriLjsg8ugvuBOuuzZJe66O8XEAXcoPh2o/SmAeg==
|
||||
"@medusajs/medusa@1.2.0-dev-1647336201011":
|
||||
version "1.2.0-dev-1647336201011"
|
||||
resolved "http://localhost:4873/@medusajs%2fmedusa/-/medusa-1.2.0-dev-1647336201011.tgz#76c0c3b93adf55b463ec091d2e144616dc1293bf"
|
||||
integrity sha512-t0eWo60Y5KsyN4ucWDjPyuI+4oOHrwqlrzegg4oogGiAfvq2rLDQFwxrfvzKlhgYtJUA58dvxMpZqG5n1NOJLQ==
|
||||
dependencies:
|
||||
"@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"
|
||||
awilix "^4.2.3"
|
||||
body-parser "^1.19.0"
|
||||
@@ -1322,8 +1322,8 @@
|
||||
joi "^17.3.0"
|
||||
joi-objectid "^3.0.1"
|
||||
jsonwebtoken "^8.5.1"
|
||||
medusa-core-utils "1.1.31-dev-1646213107704"
|
||||
medusa-test-utils "1.1.37-dev-1646213107704"
|
||||
medusa-core-utils "1.1.31-dev-1647336201011"
|
||||
medusa-test-utils "1.1.37-dev-1647336201011"
|
||||
morgan "^1.9.1"
|
||||
multer "^1.4.2"
|
||||
passport "^0.4.0"
|
||||
@@ -1947,10 +1947,10 @@ babel-preset-jest@^26.6.2:
|
||||
babel-plugin-jest-hoist "^26.6.2"
|
||||
babel-preset-current-node-syntax "^1.0.0"
|
||||
|
||||
babel-preset-medusa-package@1.1.19-dev-1646213107704:
|
||||
version "1.1.19-dev-1646213107704"
|
||||
resolved "http://localhost:4873/babel-preset-medusa-package/-/babel-preset-medusa-package-1.1.19-dev-1646213107704.tgz#820201a6abfbdb95af623f1cae8981bb5ccddb8d"
|
||||
integrity sha512-gTkFWvA/i+UmPSK8O0J4HBXE1tqQSD/D2p3ernvcvoaaXYhiLhNUrRRe9F8YXdkFT4tVXy2DKz/eQyUG1dsnnA==
|
||||
babel-preset-medusa-package@1.1.19-dev-1647336201011:
|
||||
version "1.1.19-dev-1647336201011"
|
||||
resolved "http://localhost:4873/babel-preset-medusa-package/-/babel-preset-medusa-package-1.1.19-dev-1647336201011.tgz#52a3e7ad80f216cdcae3732c22d9edb3d2504a92"
|
||||
integrity sha512-oYbWdvcXHvGc14p6N4vyGjPK0gs/zO4gVMahrDHPcMGfmGOajkVBMnuMfiEzOaDHaiBchIsWCv9LYLFLcv8JjA==
|
||||
dependencies:
|
||||
"@babel/plugin-proposal-class-properties" "^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"
|
||||
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
|
||||
|
||||
medusa-core-utils@1.1.31-dev-1646213107704:
|
||||
version "1.1.31-dev-1646213107704"
|
||||
resolved "http://localhost:4873/medusa-core-utils/-/medusa-core-utils-1.1.31-dev-1646213107704.tgz#40ca7c1a4b290b1af9cf855cfc3320c23bbf3c53"
|
||||
integrity sha512-P3D/jVSnx0YYEU8hTVqEow4Xht/BAHnHyc/HSbU5YT7PtCwEd+MY7+bvSAkhbVXNDCVsxyGTDA2nuofPS/c4RQ==
|
||||
medusa-core-utils@1.1.31-dev-1647336201011:
|
||||
version "1.1.31-dev-1647336201011"
|
||||
resolved "http://localhost:4873/medusa-core-utils/-/medusa-core-utils-1.1.31-dev-1647336201011.tgz#6b6d9c9708d8377d1bddc8ef9d26723b550312c9"
|
||||
integrity sha512-/qYyS0vE5lqHhNZfhNM2iTE5Sh8qYockVyHn2dapolBsmYE9f7IjhOsB3bz2o0b1J33L3se6iG0wSCQeN3INXA==
|
||||
dependencies:
|
||||
joi "^17.3.0"
|
||||
joi-objectid "^3.0.1"
|
||||
|
||||
medusa-interfaces@1.2.0-dev-1646213107704:
|
||||
version "1.2.0-dev-1646213107704"
|
||||
resolved "http://localhost:4873/medusa-interfaces/-/medusa-interfaces-1.2.0-dev-1646213107704.tgz#1a5310484afb5c1d127ba5775e08e3c0502e1488"
|
||||
integrity sha512-3+Ve8f5a+dvdcRafOzAuisXk/vwY9fBXgshof8xYFgtDso2INwMHTNIEgUQrjMd8yXt2+EO8l9wlBeAr02eQ0A==
|
||||
medusa-interfaces@1.2.0-dev-1647336201011:
|
||||
version "1.2.0-dev-1647336201011"
|
||||
resolved "http://localhost:4873/medusa-interfaces/-/medusa-interfaces-1.2.0-dev-1647336201011.tgz#f6a25ef007c8ffe2828b4e2f64f23bd60929f4f5"
|
||||
integrity sha512-8PvQpLfwB0YGTQU0qh/miDhJaOczoyCFs1bvlMbe3cOsn7ua7UKeUj+TLQQwh6ykbezjtggUgafEH+fKOmLUWg==
|
||||
dependencies:
|
||||
medusa-core-utils "1.1.31-dev-1646213107704"
|
||||
medusa-core-utils "1.1.31-dev-1647336201011"
|
||||
|
||||
medusa-telemetry@0.0.11-dev-1646213107704:
|
||||
version "0.0.11-dev-1646213107704"
|
||||
resolved "http://localhost:4873/medusa-telemetry/-/medusa-telemetry-0.0.11-dev-1646213107704.tgz#7dc191fd263774a31795c2696ffe015a2b5f7443"
|
||||
integrity sha512-BLKdzaU1sE9Eqnt3lmE6wkocOuC7wMT0l2OWc0aW6YTRRWDOffxOuANku1bZCIdVtbJ1q25UpeY2dwuD7lXhZw==
|
||||
medusa-telemetry@0.0.11-dev-1647336201011:
|
||||
version "0.0.11-dev-1647336201011"
|
||||
resolved "http://localhost:4873/medusa-telemetry/-/medusa-telemetry-0.0.11-dev-1647336201011.tgz#d8a33d114a5bd4e6d2bdef112a07eed96a5b2f8d"
|
||||
integrity sha512-am/sacr0XhCyc2OT6w8QCLuI7AXioJvaXxPzB4P1n5OiEGOn3IJIHZJY22MQzdY/dJZ7Rb4XuFlXdZInbc4woA==
|
||||
dependencies:
|
||||
axios "^0.21.1"
|
||||
axios-retry "^3.1.9"
|
||||
@@ -5170,13 +5170,13 @@ medusa-telemetry@0.0.11-dev-1646213107704:
|
||||
remove-trailing-slash "^0.1.1"
|
||||
uuid "^8.3.2"
|
||||
|
||||
medusa-test-utils@1.1.37-dev-1646213107704:
|
||||
version "1.1.37-dev-1646213107704"
|
||||
resolved "http://localhost:4873/medusa-test-utils/-/medusa-test-utils-1.1.37-dev-1646213107704.tgz#5fdb8298ac482f1d89475115d7727dd5227ac866"
|
||||
integrity sha512-S42x4NaioWXPwQluO+iOYDwV0LUmr7q58XClFJA8j7Nw07IFKJU2PPHAAPrbpWQKpa2pdCBYdR0FUID4/VOfXw==
|
||||
medusa-test-utils@1.1.37-dev-1647336201011:
|
||||
version "1.1.37-dev-1647336201011"
|
||||
resolved "http://localhost:4873/medusa-test-utils/-/medusa-test-utils-1.1.37-dev-1647336201011.tgz#6a1cb877abf79210db0065095b1baa0427eb93d1"
|
||||
integrity sha512-K6J7FlUm3q3jL94Ye01gpk3rcan9JEH1SDRoUm1zqZ0XAX3tl49R7VQtHO7kWcO05o2JUJt8/+LvNv6suth4ow==
|
||||
dependencies:
|
||||
"@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"
|
||||
|
||||
merge-descriptors@1.0.1:
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
import { Router } from "express"
|
||||
import cors from "cors"
|
||||
|
||||
import { Router } from "express"
|
||||
import middlewares from "../../middlewares"
|
||||
import appRoutes from "./apps"
|
||||
import authRoutes from "./auth"
|
||||
import productRoutes from "./products"
|
||||
import userRoutes, { unauthenticatedUserRoutes } from "./users"
|
||||
import collectionRoutes from "./collections"
|
||||
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 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 returnReasonRoutes from "./return-reasons"
|
||||
import returnRoutes from "./returns"
|
||||
import shippingOptionRoutes from "./shipping-options"
|
||||
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 uploadRoutes from "./uploads"
|
||||
import customerRoutes from "./customers"
|
||||
import appRoutes from "./apps"
|
||||
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 productTypesRoutes from "./product-types"
|
||||
import customerGroupRoutes from "./customer-groups"
|
||||
import uploadRoutes from "./uploads"
|
||||
import userRoutes, { unauthenticatedUserRoutes } from "./users"
|
||||
import variantRoutes from "./variants"
|
||||
|
||||
const route = Router()
|
||||
|
||||
@@ -86,6 +86,7 @@ export default (app, container, config) => {
|
||||
inviteRoutes(route)
|
||||
taxRateRoutes(route)
|
||||
customerGroupRoutes(route)
|
||||
priceListRoutes(route)
|
||||
|
||||
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,
|
||||
IsBoolean,
|
||||
IsEnum,
|
||||
IsInt,
|
||||
IsNumber,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
IsString,
|
||||
Validate,
|
||||
ValidateNested,
|
||||
} from "class-validator"
|
||||
import { EntityManager } from "typeorm"
|
||||
@@ -19,7 +17,7 @@ import {
|
||||
ShippingProfileService,
|
||||
} from "../../../../services"
|
||||
import { ProductStatus } from "../../../../types/product"
|
||||
import { XorConstraint } from "../../../../types/validators/xor"
|
||||
import { ProductVariantPricesCreateReq } from "../../../../types/product-variant"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
|
||||
/**
|
||||
@@ -308,21 +306,6 @@ class ProductOptionReq {
|
||||
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 {
|
||||
@IsString()
|
||||
title: string
|
||||
@@ -393,8 +376,8 @@ class ProductVariantReq {
|
||||
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => ProductVariantPricesReq)
|
||||
prices: ProductVariantPricesReq[]
|
||||
@Type(() => ProductVariantPricesCreateReq)
|
||||
prices: ProductVariantPricesCreateReq[]
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => ProductVariantOptionReq)
|
||||
|
||||
@@ -2,17 +2,15 @@ import { Type } from "class-transformer"
|
||||
import {
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
IsInt,
|
||||
IsNumber,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
IsString,
|
||||
Validate,
|
||||
ValidateNested,
|
||||
} from "class-validator"
|
||||
import { defaultAdminProductFields, defaultAdminProductRelations } from "."
|
||||
import { ProductService, ProductVariantService } from "../../../../services"
|
||||
import { XorConstraint } from "../../../../types/validators/xor"
|
||||
import { ProductVariantPricesCreateReq } from "../../../../types/product-variant"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
|
||||
/**
|
||||
@@ -96,8 +94,11 @@ import { validator } from "../../../../utils/validator"
|
||||
* amount:
|
||||
* description: The amount to charge for the Product Variant.
|
||||
* type: integer
|
||||
* sale_amount:
|
||||
* description: The sale amount to charge for the Product Variant.
|
||||
* 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
|
||||
* options:
|
||||
* type: array
|
||||
@@ -152,21 +153,6 @@ class ProductVariantOptionReq {
|
||||
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 {
|
||||
@IsString()
|
||||
title: string
|
||||
@@ -237,8 +223,8 @@ export class AdminPostProductsProductVariantsReq {
|
||||
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => ProductVariantPricesReq)
|
||||
prices: ProductVariantPricesReq[]
|
||||
@Type(() => ProductVariantPricesCreateReq)
|
||||
prices: ProductVariantPricesCreateReq[]
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => ProductVariantOptionReq)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Router } from "express"
|
||||
import { Product, ProductTag, ProductType } from "../../../.."
|
||||
import { DeleteResponse, PaginatedResponse } from "../../../../types/common"
|
||||
import middlewares from "../../../middlewares"
|
||||
import "reflect-metadata"
|
||||
import { Product, ProductTag, ProductType } from "../../../.."
|
||||
import { PaginatedResponse } from "../../../../types/common"
|
||||
import middlewares from "../../../middlewares"
|
||||
|
||||
const route = Router()
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
IsOptional,
|
||||
IsString,
|
||||
NotEquals,
|
||||
Validate,
|
||||
ValidateIf,
|
||||
ValidateNested,
|
||||
} from "class-validator"
|
||||
@@ -19,7 +18,7 @@ import {
|
||||
ProductStatus,
|
||||
} from "."
|
||||
import { ProductService } from "../../../../services"
|
||||
import { XorConstraint } from "../../../../types/validators/xor"
|
||||
import { ProductVariantPricesUpdateReq } from "../../../../types/product-variant"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
|
||||
/**
|
||||
@@ -248,21 +247,6 @@ class ProductVariantOptionReq {
|
||||
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 {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@@ -339,8 +323,8 @@ class ProductVariantReq {
|
||||
@IsArray()
|
||||
@IsOptional()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => ProductVariantPricesReq)
|
||||
prices: ProductVariantPricesReq[]
|
||||
@Type(() => ProductVariantPricesUpdateReq)
|
||||
prices: ProductVariantPricesUpdateReq[]
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => ProductVariantOptionReq)
|
||||
|
||||
@@ -2,17 +2,15 @@ import { Type } from "class-transformer"
|
||||
import {
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
IsInt,
|
||||
IsNumber,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
IsString,
|
||||
Validate,
|
||||
ValidateNested,
|
||||
} from "class-validator"
|
||||
import { defaultAdminProductFields, defaultAdminProductRelations } from "."
|
||||
import { ProductService, ProductVariantService } from "../../../../services"
|
||||
import { XorConstraint } from "../../../../types/validators/xor"
|
||||
import { ProductVariantPricesUpdateReq } from "../../../../types/product-variant"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
|
||||
/**
|
||||
@@ -84,6 +82,9 @@ import { validator } from "../../../../utils/validator"
|
||||
* 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
|
||||
@@ -93,8 +94,11 @@ import { validator } from "../../../../utils/validator"
|
||||
* amount:
|
||||
* description: The amount to charge for the Product Variant.
|
||||
* type: integer
|
||||
* sale_amount:
|
||||
* description: The sale amount to charge for the Product Variant.
|
||||
* 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
|
||||
* options:
|
||||
* type: array
|
||||
@@ -152,21 +156,6 @@ class ProductVariantOptionReq {
|
||||
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 {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@@ -238,8 +227,8 @@ export class AdminPostProductsProductVariantsVariantReq {
|
||||
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => ProductVariantPricesReq)
|
||||
prices: ProductVariantPricesReq[]
|
||||
@Type(() => ProductVariantPricesUpdateReq)
|
||||
prices: ProductVariantPricesUpdateReq[]
|
||||
|
||||
@Type(() => ProductVariantOptionReq)
|
||||
@ValidateNested({ each: true })
|
||||
|
||||
@@ -42,6 +42,7 @@ export * from "./models/order"
|
||||
export * from "./models/payment"
|
||||
export * from "./models/payment-provider"
|
||||
export * from "./models/payment-session"
|
||||
export * from "./models/price-list"
|
||||
export * from "./models/product"
|
||||
export * from "./models/product-collection"
|
||||
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,
|
||||
Entity,
|
||||
Index,
|
||||
JoinTable,
|
||||
ManyToMany,
|
||||
PrimaryColumn,
|
||||
UpdateDateColumn,
|
||||
UpdateDateColumn
|
||||
} from "typeorm"
|
||||
import { ulid } from "ulid"
|
||||
import { Customer } from ".."
|
||||
import { DbAwareColumn, resolveDbType } from "../utils/db-aware-column"
|
||||
import { Customer } from "./customer"
|
||||
import { PriceList } from "./price-list"
|
||||
|
||||
@Entity()
|
||||
export class CustomerGroup {
|
||||
@@ -23,22 +23,22 @@ export class CustomerGroup {
|
||||
@Column()
|
||||
name: string
|
||||
|
||||
@ManyToMany(() => Customer, (customer) => customer.groups, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
@JoinTable({
|
||||
name: "customer_group_customers",
|
||||
joinColumn: {
|
||||
name: "customer_group_id",
|
||||
referencedColumnName: "id",
|
||||
},
|
||||
inverseJoinColumn: {
|
||||
name: "customer_id",
|
||||
referencedColumnName: "id",
|
||||
},
|
||||
})
|
||||
@ManyToMany(
|
||||
() => Customer,
|
||||
(customer) => customer.groups,
|
||||
{
|
||||
onDelete: "CASCADE",
|
||||
}
|
||||
)
|
||||
customers: Customer[]
|
||||
|
||||
@ManyToMany(
|
||||
() => PriceList,
|
||||
(priceList) => priceList.customer_groups,
|
||||
{ onDelete: "CASCADE" }
|
||||
)
|
||||
price_lists: PriceList[]
|
||||
|
||||
@CreateDateColumn({ type: resolveDbType("timestamptz") })
|
||||
created_at: Date
|
||||
|
||||
|
||||
@@ -8,11 +8,12 @@ import {
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
PrimaryColumn,
|
||||
UpdateDateColumn,
|
||||
UpdateDateColumn
|
||||
} from "typeorm"
|
||||
import { ulid } from "ulid"
|
||||
import { resolveDbType } from "../utils/db-aware-column"
|
||||
import { Currency } from "./currency"
|
||||
import { PriceList } from "./price-list"
|
||||
import { ProductVariant } from "./product-variant"
|
||||
import { Region } from "./region"
|
||||
|
||||
@@ -31,16 +32,34 @@ export class MoneyAmount {
|
||||
@Column({ type: "int" })
|
||||
amount: number
|
||||
|
||||
@Column({ type: "int", nullable: true, default: null })
|
||||
sale_amount?: number
|
||||
@Column({ type: "int", nullable: true })
|
||||
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()
|
||||
@Column({ nullable: true })
|
||||
variant_id: string
|
||||
|
||||
@ManyToOne(() => ProductVariant, (variant) => variant.prices, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
@ManyToOne(
|
||||
() => ProductVariant,
|
||||
(variant) => variant.prices,
|
||||
{
|
||||
onDelete: "CASCADE",
|
||||
}
|
||||
)
|
||||
@JoinColumn({ name: "variant_id" })
|
||||
variant: ProductVariant
|
||||
|
||||
@@ -86,8 +105,11 @@ export class MoneyAmount {
|
||||
* amount:
|
||||
* description: "The amount in the smallest currecny unit (e.g. cents 100 cents to charge $1) that the Product Variant will cost."
|
||||
* type: integer
|
||||
* sale_amount:
|
||||
* description: "An optional sale amount that the Product Variant will be available for when defined."
|
||||
* min_quantity:
|
||||
* 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
|
||||
* variant_id:
|
||||
* 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 {
|
||||
Brackets,
|
||||
EntityRepository,
|
||||
In,
|
||||
IsNull,
|
||||
Not,
|
||||
Repository,
|
||||
Repository
|
||||
} from "typeorm"
|
||||
import { MoneyAmount } from "../models/money-amount"
|
||||
import { PriceListPriceCreateInput, PriceListPriceUpdateInput } from "../types/price-list"
|
||||
|
||||
type Price = Partial<
|
||||
Pick<
|
||||
MoneyAmount,
|
||||
"currency_code" | "region_id" | "sale_amount" | "currency_code"
|
||||
>
|
||||
> & { amount: number }
|
||||
Omit<MoneyAmount, "created_at" | "updated_at" | "deleted_at">
|
||||
> & {
|
||||
amount: number
|
||||
}
|
||||
|
||||
@EntityRepository(MoneyAmount)
|
||||
export class MoneyAmountRepository extends Repository<MoneyAmount> {
|
||||
@@ -21,6 +22,7 @@ export class MoneyAmountRepository extends Repository<MoneyAmount> {
|
||||
const pricesNotInPricesPayload = await this.createQueryBuilder()
|
||||
.where({
|
||||
variant_id: variantId,
|
||||
price_list_id: IsNull(),
|
||||
})
|
||||
.andWhere(
|
||||
new Brackets((qb) => {
|
||||
@@ -33,12 +35,13 @@ export class MoneyAmountRepository extends Repository<MoneyAmount> {
|
||||
return pricesNotInPricesPayload
|
||||
}
|
||||
|
||||
public async upsertCurrencyPrice(variantId: string, price: Price) {
|
||||
public async upsertVariantCurrencyPrice(variantId: string, price: Price) {
|
||||
let moneyAmount = await this.findOne({
|
||||
where: {
|
||||
currency_code: price.currency_code,
|
||||
variant_id: variantId,
|
||||
region_id: IsNull(),
|
||||
price_list_id: IsNull(),
|
||||
},
|
||||
})
|
||||
|
||||
@@ -50,9 +53,64 @@ export class MoneyAmountRepository extends Repository<MoneyAmount> {
|
||||
})
|
||||
} else {
|
||||
moneyAmount.amount = price.amount
|
||||
moneyAmount.sale_amount = price.sale_amount
|
||||
}
|
||||
|
||||
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",
|
||||
currency_code: "usd",
|
||||
amount: 100,
|
||||
sale_amount: null,
|
||||
price_list_id: null,
|
||||
min_quantity: 1,
|
||||
max_quantity: 10,
|
||||
variant_id: "variant_with_prices",
|
||||
region_id: null,
|
||||
created_at: "2021-03-16T21:24:13.657Z",
|
||||
@@ -109,7 +111,9 @@ const variantWithPrices = {
|
||||
id: "price_2",
|
||||
currency_code: "dk",
|
||||
amount: 100,
|
||||
sale_amount: null,
|
||||
price_list_id: null,
|
||||
min_quantity: 1,
|
||||
max_quantity: 10,
|
||||
variant_id: "variant_with_prices",
|
||||
region_id: null,
|
||||
created_at: "2021-03-16T21:24:13.657Z",
|
||||
@@ -285,8 +289,12 @@ export const ProductVariantServiceMock = {
|
||||
}),
|
||||
delete: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
update: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
setCurrencyPrice: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
setRegionPrice: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
updateVariantPrices: jest.fn().mockImplementation((variantId, prices) => {
|
||||
return Promise.resolve({})
|
||||
}),
|
||||
deleteVariantPrices: jest.fn().mockImplementation((variantId, priceIds) => {
|
||||
return Promise.resolve({})
|
||||
}),
|
||||
updateOptionValue: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
addOptionValue: jest.fn().mockImplementation((variantId, optionId, value) => {
|
||||
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 from "medusa-test-utils/dist/id-map"
|
||||
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
|
||||
import ProductVariantService from "../product-variant"
|
||||
|
||||
const eventBusService = {
|
||||
@@ -376,7 +375,6 @@ describe("ProductVariantService", () => {
|
||||
{
|
||||
currency_code: "dkk",
|
||||
amount: 1000,
|
||||
sale_amount: 750,
|
||||
},
|
||||
],
|
||||
})
|
||||
@@ -388,7 +386,6 @@ describe("ProductVariantService", () => {
|
||||
{
|
||||
currency_code: "dkk",
|
||||
amount: 1000,
|
||||
sale_amount: 750,
|
||||
},
|
||||
]
|
||||
)
|
||||
@@ -435,6 +432,10 @@ describe("ProductVariantService", () => {
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve(oldPrices))
|
||||
|
||||
moneyAmountRepository.deleteVariantPrices = jest
|
||||
.fn()
|
||||
.mockImplementation((variant_id, price_ids) => Promise.resolve({}))
|
||||
|
||||
const productVariantRepository = MockRepository({
|
||||
findOne: (query) => Promise.resolve({ id: IdMap.getId("ironman") }),
|
||||
})
|
||||
@@ -456,114 +457,9 @@ describe("ProductVariantService", () => {
|
||||
.fn()
|
||||
.mockReturnValue(() => Promise.resolve())
|
||||
|
||||
productVariantService.setCurrencyPrice = jest
|
||||
.fn()
|
||||
.mockReturnValue(() => Promise.resolve())
|
||||
|
||||
productVariantService.setRegionPrice = jest
|
||||
.fn()
|
||||
.mockReturnValue(() => Promise.resolve())
|
||||
|
||||
beforeEach(async () => {
|
||||
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", () => {
|
||||
@@ -590,7 +486,6 @@ describe("ProductVariantService", () => {
|
||||
currency_code: "dkk",
|
||||
region_id: IdMap.getId("california"),
|
||||
amount: 1000,
|
||||
sale_amount: 750,
|
||||
})
|
||||
}
|
||||
return Promise.resolve({
|
||||
@@ -629,7 +524,8 @@ describe("ProductVariantService", () => {
|
||||
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 () => {
|
||||
@@ -646,7 +542,7 @@ describe("ProductVariantService", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("setRegionPrice", () => {
|
||||
describe("updateVariantPrices", () => {
|
||||
const moneyAmountRepository = MockRepository({
|
||||
findOne: (query) => {
|
||||
if (query.where.region_id === IdMap.getId("cali")) {
|
||||
@@ -657,14 +553,47 @@ describe("ProductVariantService", () => {
|
||||
variant_id: IdMap.getId("ironman"),
|
||||
currency_code: "dkk",
|
||||
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({
|
||||
manager: MockManager,
|
||||
eventBusService,
|
||||
regionService,
|
||||
moneyAmountRepository,
|
||||
})
|
||||
|
||||
@@ -672,17 +601,43 @@ describe("ProductVariantService", () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("successfully creates a price if none exist with given region id", async () => {
|
||||
await productVariantService.setRegionPrice(IdMap.getId("ironman"), {
|
||||
it("successfully removes obsolete prices and calls save on new/existing prices", async () => {
|
||||
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",
|
||||
amount: 100,
|
||||
region_id: IdMap.getId("cali"),
|
||||
amount: 4000,
|
||||
})
|
||||
|
||||
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).toHaveBeenCalledWith({
|
||||
variant_id: IdMap.getId("ironman"),
|
||||
currency_code: "usd",
|
||||
region_id: IdMap.getId("cali"),
|
||||
amount: 100,
|
||||
})
|
||||
@@ -690,22 +645,26 @@ describe("ProductVariantService", () => {
|
||||
expect(moneyAmountRepository.save).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("successfully updates a price", async () => {
|
||||
await productVariantService.setRegionPrice(IdMap.getId("ironman"), {
|
||||
currency_code: "dkk",
|
||||
amount: 750,
|
||||
sale_amount: 500,
|
||||
})
|
||||
it("successfully creates a currency price", async () => {
|
||||
await productVariantService.updateVariantPrices(IdMap.getId("ironman"), [
|
||||
{
|
||||
id: IdMap.getId("dkk"),
|
||||
currency_code: "dkk",
|
||||
amount: 750,
|
||||
},
|
||||
])
|
||||
|
||||
expect(moneyAmountRepository.create).toHaveBeenCalledTimes(0)
|
||||
|
||||
expect(moneyAmountRepository.save).toHaveBeenCalledTimes(1)
|
||||
expect(moneyAmountRepository.save).toHaveBeenCalledWith({
|
||||
variant_id: IdMap.getId("ironman"),
|
||||
expect(
|
||||
moneyAmountRepository.upsertVariantCurrencyPrice
|
||||
).toHaveBeenCalledTimes(1)
|
||||
expect(
|
||||
moneyAmountRepository.upsertVariantCurrencyPrice
|
||||
).toHaveBeenCalledWith(IdMap.getId("ironman"), {
|
||||
id: IdMap.getId("dkk"),
|
||||
currency_code: "dkk",
|
||||
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 { MedusaError, Validator } from "medusa-core-utils"
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
import { Brackets, ILike } from "typeorm"
|
||||
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 { BaseService } from "medusa-interfaces"
|
||||
import { Brackets, EntityManager, ILike, In, SelectQueryBuilder } from "typeorm"
|
||||
import { MoneyAmount } from "../models/money-amount"
|
||||
import { Brackets, EntityManager, ILike, SelectQueryBuilder } from "typeorm"
|
||||
import { MoneyAmount } from ".."
|
||||
import { Product } from "../models/product"
|
||||
import { ProductOptionValue } from "../models/product-option-value"
|
||||
import { ProductVariant } from "../models/product-variant"
|
||||
@@ -230,7 +230,6 @@ class ProductVariantService extends BaseService {
|
||||
await this.setRegionPrice(result.id, {
|
||||
amount: price.amount,
|
||||
region_id: price.region_id,
|
||||
sale_amount: price.sale_amount,
|
||||
})
|
||||
} else {
|
||||
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(
|
||||
variantId: string,
|
||||
prices: ProductVariantPrice[]
|
||||
@@ -337,7 +343,6 @@ class ProductVariantService extends BaseService {
|
||||
await this.setRegionPrice(variantId, {
|
||||
region_id: price.region_id,
|
||||
amount: price.amount,
|
||||
sale_amount: price.sale_amount || undefined,
|
||||
})
|
||||
} else {
|
||||
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
|
||||
* 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
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 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} price - the price for the variant.
|
||||
* @return {Promise} the result of the update operation
|
||||
@@ -434,6 +415,7 @@ class ProductVariantService extends BaseService {
|
||||
where: {
|
||||
variant_id: variantId,
|
||||
region_id: price.region_id,
|
||||
price_list_id: null,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -444,7 +426,6 @@ class ProductVariantService extends BaseService {
|
||||
})
|
||||
} else {
|
||||
moneyAmount.amount = price.amount
|
||||
moneyAmount.sale_amount = price.sale_amount
|
||||
}
|
||||
|
||||
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.
|
||||
* Option value must be of type string or number.
|
||||
@@ -636,7 +636,10 @@ class ProductVariantService extends BaseService {
|
||||
this.productVariantRepository_
|
||||
)
|
||||
|
||||
const variant = await variantRepo.findOne({ where: { id: variantId } })
|
||||
const variant = await variantRepo.findOne({
|
||||
where: { id: variantId },
|
||||
relations: ["prices"],
|
||||
})
|
||||
|
||||
if (!variant) {
|
||||
return Promise.resolve()
|
||||
@@ -662,7 +665,10 @@ class ProductVariantService extends BaseService {
|
||||
* @param {Object} metadata - the metadata to set
|
||||
* @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 newData = {}
|
||||
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 {
|
||||
DateComparisonOperator,
|
||||
NumericalComparisonOperator,
|
||||
StringComparisonOperator,
|
||||
} from "./common"
|
||||
import { XorConstraint } from "./validators/xor"
|
||||
|
||||
export type ProductVariantPrice = {
|
||||
id?: string
|
||||
currency_code?: string
|
||||
region_id?: string
|
||||
amount: number
|
||||
sale_amount?: number | undefined
|
||||
min_quantity?: number
|
||||
max_quantity?: number
|
||||
}
|
||||
|
||||
export type ProductVariantOption = {
|
||||
@@ -130,3 +142,47 @@ export class FilterableProductVariantProps {
|
||||
@IsType([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