From 9288f53327b8ce617af92ed8d14d9459cbfeb13c Mon Sep 17 00:00:00 2001 From: Riqwan Thamir Date: Fri, 15 Mar 2024 12:09:02 +0100 Subject: [PATCH] feat(core-flows,medusa,pricing,types,utils): added price list workflows + endpoints (#6648) Apologies for the giant PR in advance, but most of these are removing of files and migrations from old workflows and routes to new. What: - Adds CRUD endpoints for price lists - Migrate workflows from old sdk to new - Added missing updatePriceListPrices method to pricing module --- .changeset/blue-taxis-bathe.md | 7 + .changeset/nasty-waves-sparkle.md | 9 + .../admin/create-price-list.spec.ts | 175 --------- .../admin/delete-price-list-spec.ts | 128 ------- .../price-lists/admin/get-price-list.spec.ts | 184 ---------- .../price-lists/admin/list-price-list.spec.ts | 152 -------- .../price-lists/admin/price-lists.spec.ts | 345 ++++++++++++++++++ .../admin/update-price-list.spec.ts | 265 -------------- packages/core-flows/src/definition/index.ts | 1 - .../price-list/create-price-lists.ts | 74 ---- .../price-list/remove-price-list-prices.ts | 71 ---- .../price-list/remove-price-lists.ts | 57 --- .../price-list/remove-product-prices.ts | 72 ---- .../price-list/remove-variant-prices.ts | 72 ---- .../price-list/update-price-lists.ts | 65 ---- packages/core-flows/src/handlers/index.ts | 1 - .../handlers/price-list/create-price-list.ts | 42 --- .../src/handlers/price-list/index.ts | 10 - .../price-list/prepare-create-price-list.ts | 108 ------ .../prepare-remove-price-list-prices.ts | 49 --- .../prepare-remove-product-prices.ts | 63 ---- .../prepare-remove-variant-prices.ts | 57 --- .../price-list/prepare-update-price-lists.ts | 108 ------ .../handlers/price-list/remove-price-list.ts | 31 -- .../remove-price-set-price-list-prices.ts | 40 -- .../src/handlers/price-list/remove-prices.ts | 29 -- .../handlers/price-list/update-price-lists.ts | 63 ---- packages/core-flows/src/index.ts | 1 + packages/core-flows/src/price-list/index.ts | 2 + .../price-list/steps/create-price-lists.ts | 58 +++ .../price-list/steps/delete-price-lists.ts | 30 ++ .../core-flows/src/price-list/steps/index.ts | 7 + .../steps/remove-price-list-prices.ts | 41 +++ .../price-list/steps/update-price-lists.ts | 105 ++++++ .../steps/upsert-price-list-prices.ts | 131 +++++++ .../price-list/steps/validate-price-lists.ts | 41 +++ .../steps/validate-variant-price-links.ts | 48 +++ .../workflows/create-price-lists.ts | 20 + .../workflows/delete-price-lists.ts | 12 + .../workflows}/index.ts | 5 +- .../workflows/remove-price-list-prices.ts | 12 + .../workflows/update-price-lists.ts | 47 +++ .../workflows/upsert-price-list-prices.ts | 31 ++ .../src/api-v2/admin/campaigns/validators.ts | 5 +- .../admin/price-lists/[id]/prices/route.ts | 68 ++++ .../api-v2/admin/price-lists/[id]/route.ts | 59 ++- .../api-v2/admin/price-lists/middlewares.ts | 26 +- .../price-lists/queries/list-price-lists.ts | 51 +-- .../src/api-v2/admin/price-lists/route.ts | 35 +- .../api-v2/admin/price-lists/validators.ts | 148 ++++++++ .../admin/price-lists/add-prices-batch.ts | 48 +-- .../admin/price-lists/create-price-list.ts | 68 +--- .../admin/price-lists/delete-price-list.ts | 34 +- .../admin/price-lists/delete-prices-batch.ts | 38 +- .../price-lists/delete-product-prices.ts | 44 +-- .../delete-products-prices-batch.ts | 42 +-- .../price-lists/delete-variant-prices.ts | 45 +-- .../price-lists/list-price-list-products.ts | 25 +- .../admin/price-lists/update-price-list.ts | 57 +-- .../src/scripts/migrate-to-pricing-module.ts | 6 +- .../pricing-module/calculate-price.spec.ts | 6 +- .../pricing-module/price-list-rule.spec.ts | 10 +- .../pricing-module/price-list.spec.ts | 205 ++++++++++- .../services/pricing-module/price-set.spec.ts | 4 +- .../pricing/src/services/pricing-module.ts | 214 +++++++++-- .../src/types/repositories/price-list.ts | 8 +- .../src/types/services/money-amount.ts | 8 +- .../pricing/src/types/services/price-list.ts | 10 +- .../types/services/price-set-money-amount.ts | 1 + .../types/src/pricing/common/money-amount.ts | 2 +- .../types/src/pricing/common/price-list.ts | 60 ++- .../types/src/pricing/common/price-rule.ts | 13 - .../pricing/common/price-set-money-amount.ts | 10 +- packages/types/src/pricing/index.ts | 1 + packages/types/src/pricing/service.ts | 8 + packages/types/src/pricing/workflows.ts | 41 +++ ...selects-and-relations-from-object-array.ts | 7 +- packages/utils/src/pricing/builders.ts | 70 ++++ packages/utils/src/pricing/index.ts | 1 + .../src/utils/composer/create-step.ts | 4 +- .../utils/composer/helpers/step-response.ts | 18 +- 81 files changed, 1959 insertions(+), 2410 deletions(-) create mode 100644 .changeset/blue-taxis-bathe.md create mode 100644 .changeset/nasty-waves-sparkle.md delete mode 100644 integration-tests/modules/__tests__/price-lists/admin/create-price-list.spec.ts delete mode 100644 integration-tests/modules/__tests__/price-lists/admin/delete-price-list-spec.ts delete mode 100644 integration-tests/modules/__tests__/price-lists/admin/get-price-list.spec.ts delete mode 100644 integration-tests/modules/__tests__/price-lists/admin/list-price-list.spec.ts delete mode 100644 integration-tests/modules/__tests__/price-lists/admin/update-price-list.spec.ts delete mode 100644 packages/core-flows/src/definition/price-list/create-price-lists.ts delete mode 100644 packages/core-flows/src/definition/price-list/remove-price-list-prices.ts delete mode 100644 packages/core-flows/src/definition/price-list/remove-price-lists.ts delete mode 100644 packages/core-flows/src/definition/price-list/remove-product-prices.ts delete mode 100644 packages/core-flows/src/definition/price-list/remove-variant-prices.ts delete mode 100644 packages/core-flows/src/definition/price-list/update-price-lists.ts delete mode 100644 packages/core-flows/src/handlers/price-list/create-price-list.ts delete mode 100644 packages/core-flows/src/handlers/price-list/index.ts delete mode 100644 packages/core-flows/src/handlers/price-list/prepare-create-price-list.ts delete mode 100644 packages/core-flows/src/handlers/price-list/prepare-remove-price-list-prices.ts delete mode 100644 packages/core-flows/src/handlers/price-list/prepare-remove-product-prices.ts delete mode 100644 packages/core-flows/src/handlers/price-list/prepare-remove-variant-prices.ts delete mode 100644 packages/core-flows/src/handlers/price-list/prepare-update-price-lists.ts delete mode 100644 packages/core-flows/src/handlers/price-list/remove-price-list.ts delete mode 100644 packages/core-flows/src/handlers/price-list/remove-price-set-price-list-prices.ts delete mode 100644 packages/core-flows/src/handlers/price-list/remove-prices.ts delete mode 100644 packages/core-flows/src/handlers/price-list/update-price-lists.ts create mode 100644 packages/core-flows/src/price-list/index.ts create mode 100644 packages/core-flows/src/price-list/steps/create-price-lists.ts create mode 100644 packages/core-flows/src/price-list/steps/delete-price-lists.ts create mode 100644 packages/core-flows/src/price-list/steps/index.ts create mode 100644 packages/core-flows/src/price-list/steps/remove-price-list-prices.ts create mode 100644 packages/core-flows/src/price-list/steps/update-price-lists.ts create mode 100644 packages/core-flows/src/price-list/steps/upsert-price-list-prices.ts create mode 100644 packages/core-flows/src/price-list/steps/validate-price-lists.ts create mode 100644 packages/core-flows/src/price-list/steps/validate-variant-price-links.ts create mode 100644 packages/core-flows/src/price-list/workflows/create-price-lists.ts create mode 100644 packages/core-flows/src/price-list/workflows/delete-price-lists.ts rename packages/core-flows/src/{definition/price-list => price-list/workflows}/index.ts (50%) create mode 100644 packages/core-flows/src/price-list/workflows/remove-price-list-prices.ts create mode 100644 packages/core-flows/src/price-list/workflows/update-price-lists.ts create mode 100644 packages/core-flows/src/price-list/workflows/upsert-price-list-prices.ts create mode 100644 packages/medusa/src/api-v2/admin/price-lists/[id]/prices/route.ts create mode 100644 packages/types/src/pricing/workflows.ts create mode 100644 packages/utils/src/pricing/builders.ts diff --git a/.changeset/blue-taxis-bathe.md b/.changeset/blue-taxis-bathe.md new file mode 100644 index 0000000000..3f9e512669 --- /dev/null +++ b/.changeset/blue-taxis-bathe.md @@ -0,0 +1,7 @@ +--- +"@medusajs/core-flows": patch +"@medusajs/medusa": patch +"@medusajs/utils": patch +--- + +feat(core-flows,medusa,utils): added price list prices upsert and delete workflows + endpoints diff --git a/.changeset/nasty-waves-sparkle.md b/.changeset/nasty-waves-sparkle.md new file mode 100644 index 0000000000..12c127face --- /dev/null +++ b/.changeset/nasty-waves-sparkle.md @@ -0,0 +1,9 @@ +--- +"@medusajs/core-flows": patch +"@medusajs/pricing": patch +"@medusajs/medusa": patch +"@medusajs/types": patch +"@medusajs/utils": patch +--- + +feat(core-flows,medusa,pricing,types,utils): added price list workflows + endpoints diff --git a/integration-tests/modules/__tests__/price-lists/admin/create-price-list.spec.ts b/integration-tests/modules/__tests__/price-lists/admin/create-price-list.spec.ts deleted file mode 100644 index c1d88bf363..0000000000 --- a/integration-tests/modules/__tests__/price-lists/admin/create-price-list.spec.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { - simpleCustomerGroupFactory, - simpleProductFactory, - simpleRegionFactory, -} from "../../../../factories" - -import { IPricingModuleService } from "@medusajs/types" -import adminSeeder from "../../../../helpers/admin-seeder" -import { createDefaultRuleTypes } from "../../../helpers/create-default-rule-types" -import { createVariantPriceSet } from "../../../helpers/create-variant-price-set" -import { medusaIntegrationTestRunner } from "medusa-test-utils" - -jest.setTimeout(50000) - -const adminHeaders = { - headers: { - "x-medusa-access-token": "test_token", - }, -} - -const env = { - MEDUSA_FF_MEDUSA_V2: true, -} - -medusaIntegrationTestRunner({ - env, - testSuite: ({ dbConnection, getContainer, api }) => { - describe.skip("POST /admin/price-lists", () => { - let appContainer - let product - let variant - let pricingModuleService: IPricingModuleService - - beforeAll(async () => { - appContainer = getContainer() - pricingModuleService = appContainer.resolve("pricingModuleService") - }) - - beforeEach(async () => { - await adminSeeder(dbConnection) - await createDefaultRuleTypes(appContainer) - await simpleCustomerGroupFactory(dbConnection, { - id: "customer-group-1", - name: "Test Group", - }) - - await simpleRegionFactory(dbConnection, { - id: "test-region", - name: "Test Region", - currency_code: "usd", - tax_rate: 0, - }) - - product = await simpleProductFactory(dbConnection, { - id: "test-product-with-variant", - variants: [ - { - options: [{ option_id: "test-product-option-1", value: "test" }], - }, - ], - options: [ - { - id: "test-product-option-1", - title: "Test option 1", - }, - ], - }) - - variant = product.variants[0] - }) - - it("should create price list and money amounts", async () => { - await createVariantPriceSet({ - container: appContainer, - variantId: variant.id, - prices: [ - { - amount: 3000, - currency_code: "usd", - }, - ], - }) - - const data = { - name: "test price list", - description: "test", - type: "override", - customer_groups: [{ id: "customer-group-1" }], - status: "active", - starts_at: new Date(), - prices: [ - { - amount: 400, - variant_id: variant.id, - currency_code: "usd", - }, - ], - } - - const result = await api.post(`admin/price-lists`, data, adminHeaders) - - let response = await api.get( - `/admin/price-lists/${result.data.price_list.id}`, - adminHeaders - ) - - expect(response.status).toEqual(200) - expect(response.data.price_list).toEqual( - expect.objectContaining({ - id: expect.any(String), - created_at: expect.any(String), - updated_at: expect.any(String), - deleted_at: null, - name: "test price list", - description: "test", - type: "override", - status: "active", - starts_at: expect.any(String), - ends_at: null, - customer_groups: [ - { - id: expect.any(String), - created_at: expect.any(String), - updated_at: expect.any(String), - deleted_at: null, - name: "Test Group", - metadata: null, - }, - ], - prices: [ - expect.objectContaining({ - id: expect.any(String), - created_at: expect.any(String), - updated_at: expect.any(String), - deleted_at: null, - currency_code: "usd", - amount: 400, - min_quantity: null, - max_quantity: null, - price_list_id: expect.any(String), - region_id: null, - variant: expect.objectContaining({ - id: expect.any(String), - created_at: expect.any(String), - updated_at: expect.any(String), - deleted_at: null, - title: expect.any(String), - product_id: expect.any(String), - sku: null, - barcode: null, - ean: null, - upc: null, - variant_rank: 0, - inventory_quantity: 10, - allow_backorder: false, - manage_inventory: true, - hs_code: null, - origin_country: null, - mid_code: null, - material: null, - weight: null, - length: null, - height: null, - width: null, - metadata: null, - }), - variant_id: expect.any(String), - }), - ], - }) - ) - }) - }) - }, -}) diff --git a/integration-tests/modules/__tests__/price-lists/admin/delete-price-list-spec.ts b/integration-tests/modules/__tests__/price-lists/admin/delete-price-list-spec.ts deleted file mode 100644 index b7a7a01325..0000000000 --- a/integration-tests/modules/__tests__/price-lists/admin/delete-price-list-spec.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { - simpleProductFactory, - simpleRegionFactory, -} from "../../../../factories" - -import { IPricingModuleService } from "@medusajs/types" -import adminSeeder from "../../../../helpers/admin-seeder" -import { createDefaultRuleTypes } from "../../../helpers/create-default-rule-types" -import { createVariantPriceSet } from "../../../helpers/create-variant-price-set" -import { medusaIntegrationTestRunner } from "medusa-test-utils" - -jest.setTimeout(50000) - -const adminHeaders = { - headers: { - "x-medusa-access-token": "test_token", - }, -} - -const env = { - MEDUSA_FF_MEDUSA_V2: true, -} - -medusaIntegrationTestRunner({ - env, - testSuite: ({ dbConnection, getContainer, api }) => { - describe.skip("DELETE /admin/price-lists/:id", () => { - let appContainer - let product - let variant - let pricingModuleService: IPricingModuleService - - beforeAll(async () => { - appContainer = getContainer() - pricingModuleService = appContainer.resolve("pricingModuleService") - }) - - beforeEach(async () => { - await adminSeeder(dbConnection) - await createDefaultRuleTypes(appContainer) - - await simpleRegionFactory(dbConnection, { - id: "test-region", - name: "Test Region", - currency_code: "usd", - tax_rate: 0, - }) - - product = await simpleProductFactory(dbConnection, { - id: "test-product-with-variant", - variants: [ - { - options: [{ option_id: "test-product-option-1", value: "test" }], - }, - ], - options: [ - { - id: "test-product-option-1", - title: "Test option 1", - }, - ], - }) - - variant = product.variants[0] - }) - - it("should delete price list and money amounts", async () => { - const priceSet = await createVariantPriceSet({ - container: appContainer, - variantId: variant.id, - prices: [ - { - amount: 3000, - currency_code: "usd", - }, - ], - }) - - const data = { - name: "test price list", - description: "test", - type: "override", - customer_groups: [], - status: "active", - prices: [ - { - amount: 400, - variant_id: variant.id, - currency_code: "usd", - }, - ], - } - - const result = await api.post(`admin/price-lists`, data, adminHeaders) - const priceListId = result.data.price_list.id - - const getResponse = await api.get( - `/admin/price-lists/${priceListId}`, - adminHeaders - ) - expect(getResponse.status).toEqual(200) - - let psmas = await pricingModuleService.listPriceSetMoneyAmounts({ - price_list_id: [priceListId], - }) - expect(psmas.length).toEqual(1) - - const deleteRes = await api.delete( - `/admin/price-lists/${priceListId}`, - adminHeaders - ) - expect(deleteRes.status).toEqual(200) - - const afterDelete = await api - .get(`/admin/price-lists/${priceListId}`, adminHeaders) - .catch((err) => { - return err - }) - expect(afterDelete.response.status).toEqual(404) - - psmas = await pricingModuleService.listPriceSetMoneyAmounts({ - price_list_id: [priceListId], - }) - expect(psmas.length).toEqual(0) - }) - }) - }, -}) diff --git a/integration-tests/modules/__tests__/price-lists/admin/get-price-list.spec.ts b/integration-tests/modules/__tests__/price-lists/admin/get-price-list.spec.ts deleted file mode 100644 index 664ba69a3a..0000000000 --- a/integration-tests/modules/__tests__/price-lists/admin/get-price-list.spec.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { simpleProductFactory } from "../../../../factories" - -import { - IPricingModuleService, - PriceListStatus, - PriceListType, -} from "@medusajs/types" -import adminSeeder from "../../../../helpers/admin-seeder" -import { createVariantPriceSet } from "../../../helpers/create-variant-price-set" -import { medusaIntegrationTestRunner } from "medusa-test-utils" - -jest.setTimeout(50000) - -const adminHeaders = { - headers: { - "x-medusa-access-token": "test_token", - }, -} - -const env = { - MEDUSA_FF_MEDUSA_V2: true, -} - -medusaIntegrationTestRunner({ - env, - testSuite: ({ dbConnection, getContainer, api }) => { - describe.skip("GET /admin/price-lists/:id", () => { - let appContainer - let product - let variant - let pricingModuleService: IPricingModuleService - - beforeAll(async () => { - appContainer = getContainer() - pricingModuleService = appContainer.resolve("pricingModuleService") - }) - - beforeEach(async () => { - await adminSeeder(dbConnection) - - product = await simpleProductFactory(dbConnection, { - id: "test-product-with-variant", - variants: [ - { - options: [{ option_id: "test-product-option-1", value: "test" }], - }, - ], - options: [ - { - id: "test-product-option-1", - title: "Test option 1", - }, - ], - }) - - variant = product.variants[0] - }) - - it("should get price list and its money amounts with variants", async () => { - const priceSet = await createVariantPriceSet({ - container: appContainer, - variantId: variant.id, - prices: [ - { - amount: 3000, - currency_code: "usd", - }, - ], - rules: [], - }) - - const [priceList] = await pricingModuleService.createPriceLists([ - { - title: "test price list", - description: "test", - ends_at: new Date(), - starts_at: new Date(), - status: PriceListStatus.ACTIVE, - type: PriceListType.OVERRIDE, - prices: [ - { - amount: 5000, - currency_code: "usd", - price_set_id: priceSet.id, - }, - ], - }, - ]) - - await pricingModuleService.createPriceLists([ - { - title: "test price list 1", - description: "test 1", - ends_at: new Date(), - starts_at: new Date(), - status: PriceListStatus.ACTIVE, - type: PriceListType.OVERRIDE, - prices: [ - { - amount: 5000, - currency_code: "usd", - price_set_id: priceSet.id, - }, - ], - }, - ]) - - const response = await api.get( - `/admin/price-lists/${priceList.id}`, - adminHeaders - ) - - expect(response.status).toEqual(200) - expect(response.data.price_list).toEqual( - expect.objectContaining({ - id: expect.any(String), - created_at: expect.any(String), - updated_at: expect.any(String), - deleted_at: null, - name: "test price list", - description: "test", - type: "override", - status: "active", - starts_at: expect.any(String), - ends_at: expect.any(String), - customer_groups: [], - prices: [ - expect.objectContaining({ - id: expect.any(String), - created_at: expect.any(String), - updated_at: expect.any(String), - deleted_at: null, - currency_code: "usd", - amount: 5000, - min_quantity: null, - max_quantity: null, - price_list_id: expect.any(String), - region_id: null, - variant: expect.objectContaining({ - id: expect.any(String), - created_at: expect.any(String), - updated_at: expect.any(String), - deleted_at: null, - title: expect.any(String), - product_id: expect.any(String), - sku: null, - barcode: null, - ean: null, - upc: null, - variant_rank: 0, - inventory_quantity: 10, - allow_backorder: false, - manage_inventory: true, - hs_code: null, - origin_country: null, - mid_code: null, - material: null, - weight: null, - length: null, - height: null, - width: null, - metadata: null, - }), - variant_id: expect.any(String), - }), - ], - }) - ) - }) - - it("should throw an error when price list is not found", async () => { - const error = await api - .get(`/admin/price-lists/does-not-exist`, adminHeaders) - .catch((e) => e) - - expect(error.response.status).toBe(404) - expect(error.response.data).toEqual({ - type: "not_found", - message: "Price list with id: does-not-exist was not found", - }) - }) - }) - }, -}) diff --git a/integration-tests/modules/__tests__/price-lists/admin/list-price-list.spec.ts b/integration-tests/modules/__tests__/price-lists/admin/list-price-list.spec.ts deleted file mode 100644 index 1bc09f203e..0000000000 --- a/integration-tests/modules/__tests__/price-lists/admin/list-price-list.spec.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { simpleProductFactory } from "../../../../factories" - -import { - IPricingModuleService, - PriceListStatus, - PriceListType, -} from "@medusajs/types" -import adminSeeder from "../../../../helpers/admin-seeder" -import { createVariantPriceSet } from "../../../helpers/create-variant-price-set" -import { medusaIntegrationTestRunner } from "medusa-test-utils" - -jest.setTimeout(50000) - -const adminHeaders = { - headers: { - "x-medusa-access-token": "test_token", - }, -} - -const env = { - MEDUSA_FF_MEDUSA_V2: true, -} - -medusaIntegrationTestRunner({ - env, - testSuite: ({ dbConnection, getContainer, api }) => { - describe.skip("GET /admin/price-lists", () => { - let appContainer - let product - let variant - let pricingModuleService: IPricingModuleService - - beforeAll(async () => { - appContainer = getContainer() - pricingModuleService = appContainer.resolve("pricingModuleService") - }) - - beforeEach(async () => { - await adminSeeder(dbConnection) - - product = await simpleProductFactory(dbConnection, { - id: "test-product-with-variant", - variants: [ - { - options: [{ option_id: "test-product-option-1", value: "test" }], - }, - ], - options: [ - { - id: "test-product-option-1", - title: "Test option 1", - }, - ], - }) - - variant = product.variants[0] - }) - - it("should get price list and its money amounts with variants", async () => { - const priceSet = await createVariantPriceSet({ - container: appContainer, - variantId: variant.id, - prices: [ - { - amount: 3000, - currency_code: "usd", - }, - ], - rules: [], - }) - - const [priceList] = await pricingModuleService.createPriceLists([ - { - title: "test price list", - description: "test", - ends_at: new Date(), - starts_at: new Date(), - status: PriceListStatus.ACTIVE, - type: PriceListType.OVERRIDE, - prices: [ - { - amount: 5000, - currency_code: "usd", - price_set_id: priceSet.id, - }, - ], - }, - ]) - - const response = await api.get(`/admin/price-lists`, adminHeaders) - - expect(response.status).toEqual(200) - expect(response.data.count).toEqual(1) - expect(response.data.price_lists).toEqual([ - expect.objectContaining({ - id: expect.any(String), - created_at: expect.any(String), - updated_at: expect.any(String), - deleted_at: null, - name: "test price list", - description: "test", - type: "override", - status: "active", - starts_at: expect.any(String), - ends_at: expect.any(String), - customer_groups: [], - prices: [ - expect.objectContaining({ - id: expect.any(String), - created_at: expect.any(String), - updated_at: expect.any(String), - deleted_at: null, - currency_code: "usd", - amount: 5000, - min_quantity: null, - max_quantity: null, - price_list_id: expect.any(String), - region_id: null, - variant: expect.objectContaining({ - id: expect.any(String), - created_at: expect.any(String), - updated_at: expect.any(String), - deleted_at: null, - title: expect.any(String), - product_id: expect.any(String), - sku: null, - barcode: null, - ean: null, - upc: null, - variant_rank: 0, - inventory_quantity: 10, - allow_backorder: false, - manage_inventory: true, - hs_code: null, - origin_country: null, - mid_code: null, - material: null, - weight: null, - length: null, - height: null, - width: null, - metadata: null, - }), - variant_id: expect.any(String), - }), - ], - }), - ]) - }) - }) - }, -}) diff --git a/integration-tests/modules/__tests__/price-lists/admin/price-lists.spec.ts b/integration-tests/modules/__tests__/price-lists/admin/price-lists.spec.ts index fb73eb1aa8..caebcbf177 100644 --- a/integration-tests/modules/__tests__/price-lists/admin/price-lists.spec.ts +++ b/integration-tests/modules/__tests__/price-lists/admin/price-lists.spec.ts @@ -258,6 +258,351 @@ medusaIntegrationTestRunner({ }) }) }) + + describe("POST /admin/price-lists", () => { + it("should create price list and money amounts", async () => { + await createVariantPriceSet({ + container: appContainer, + variantId: variant.id, + prices: [{ amount: 3000, currency_code: "usd" }], + }) + + const data = { + title: "test price list", + description: "test", + type: "override", + status: "active", + starts_at: new Date(), + rules: { + customer_group_id: [customerGroup.id], + }, + prices: [ + { + amount: 400, + variant_id: variant.id, + currency_code: "usd", + rules: { region_id: region.id }, + }, + ], + } + + const response = await api.post( + `admin/price-lists`, + data, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.price_list).toEqual( + expect.objectContaining({ + id: expect.any(String), + created_at: expect.any(String), + updated_at: expect.any(String), + title: "test price list", + description: "test", + type: "override", + status: "active", + starts_at: expect.any(String), + ends_at: null, + rules: { + customer_group_id: [customerGroup.id], + }, + prices: [ + expect.objectContaining({ + id: expect.any(String), + currency_code: "usd", + amount: 400, + min_quantity: null, + max_quantity: null, + variant_id: variant.id, + rules: { + region_id: region.id, + }, + }), + ], + }) + ) + }) + + it("should throw error when required attributes are not provided", async () => { + const data = { + prices: [ + { + amount: 400, + currency_code: "usd", + }, + ], + } + + const errorResponse = await api + .post(`admin/price-lists`, data, adminHeaders) + .catch((e) => e) + + expect(errorResponse.response.status).toEqual(400) + expect(errorResponse.response.data.message).toEqual( + "title must be a string, description must be a string, type must be one of the following values: sale, override, variant_id must be a string" + ) + }) + }) + + describe("DELETE /admin/price-lists/:id", () => { + it("should delete price list and money amounts", async () => { + await createVariantPriceSet({ + container: appContainer, + variantId: variant.id, + prices: [{ amount: 3000, currency_code: "usd" }], + }) + + const data = { + title: "test price list", + description: "test", + type: "override", + status: "active", + starts_at: new Date(), + prices: [ + { amount: 400, variant_id: variant.id, currency_code: "usd" }, + ], + } + + const result = await api.post(`admin/price-lists`, data, adminHeaders) + const priceListId = result.data.price_list.id + + let psmas = await pricingModule.listPriceSetMoneyAmounts({ + price_list_id: [priceListId], + }) + + expect(psmas.length).toEqual(1) + + const deleteRes = await api.delete( + `/admin/price-lists/${priceListId}`, + adminHeaders + ) + + expect(deleteRes.status).toEqual(200) + + const afterDelete = await api + .get(`/admin/price-lists/${priceListId}`, adminHeaders) + .catch((e) => e) + + expect(afterDelete.response.status).toEqual(404) + + psmas = await pricingModule.listPriceSetMoneyAmounts({ + price_list_id: [priceListId], + }) + expect(psmas.length).toEqual(0) + }) + + it("should idempotently return a success even if price lists dont exist", async () => { + const deleteRes = await api.delete( + `/admin/price-lists/does-not-exist`, + adminHeaders + ) + + expect(deleteRes.status).toEqual(200) + expect(deleteRes.data).toEqual({ + id: "does-not-exist", + object: "price_list", + deleted: true, + }) + }) + }) + + describe("POST /admin/price-lists/:id", () => { + it("should throw error when trying to update a price list that does not exist", async () => { + const updateRes = await api + .post( + `admin/price-lists/does-not-exist`, + { title: "new price list name" }, + adminHeaders + ) + .catch((e) => e) + + expect(updateRes.response.status).toEqual(404) + expect(updateRes.response.data.message).toEqual( + "Price lists with id: does-not-exist was not found" + ) + }) + + it("should update price lists successfully", async () => { + await createVariantPriceSet({ + container: appContainer, + variantId: variant.id, + prices: [{ amount: 3000, currency_code: "usd" }], + }) + + const [priceList] = await pricingModule.createPriceLists([ + { + title: "test price list", + description: "test", + ends_at: new Date(), + starts_at: new Date(), + status: PriceListStatus.ACTIVE, + type: PriceListType.OVERRIDE, + }, + ]) + + const data = { + title: "new price list name", + description: "new price list description", + rules: { + customer_group_id: [customerGroup.id], + }, + prices: [ + { + amount: 400, + variant_id: variant.id, + currency_code: "usd", + rules: { region_id: region.id }, + }, + ], + } + + const response = await api.post( + `admin/price-lists/${priceList.id}`, + data, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.price_list).toEqual( + expect.objectContaining({ + id: expect.any(String), + created_at: expect.any(String), + updated_at: expect.any(String), + title: "new price list name", + description: "new price list description", + type: "override", + status: "active", + starts_at: expect.any(String), + ends_at: expect.any(String), + rules: { + customer_group_id: [customerGroup.id], + }, + prices: [ + { + id: expect.any(String), + currency_code: "usd", + amount: 400, + min_quantity: null, + max_quantity: null, + variant_id: variant.id, + rules: { + region_id: region.id, + }, + }, + ], + }) + ) + }) + }) + + describe("POST /admin/price-lists/:id/prices", () => { + it("should upsert price list prices successfully", async () => { + const priceSet = await createVariantPriceSet({ + container: appContainer, + variantId: variant.id, + prices: [{ amount: 3000, currency_code: "usd" }], + }) + + const [priceList] = await pricingModule.createPriceLists([ + { + title: "test price list", + description: "test", + prices: [ + { + id: "test-price-id", + amount: 5000, + currency_code: "usd", + price_set_id: priceSet.id, + rules: { region_id: region.id }, + }, + ], + }, + ]) + + const data = { + prices: [ + { + amount: 400, + variant_id: variant.id, + currency_code: "usd", + rules: { region_id: region.id }, + }, + { + id: "test-price-id", + variant_id: variant.id, + amount: 200, + }, + ], + } + + const response = await api.post( + `admin/price-lists/${priceList.id}/prices`, + data, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.price_list.prices.length).toEqual(2) + expect(response.data.price_list).toEqual( + expect.objectContaining({ + id: expect.any(String), + prices: expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + currency_code: "usd", + amount: 400, + }), + expect.objectContaining({ + id: "test-price-id", + currency_code: "usd", + amount: 200, + }), + ]), + }) + ) + }) + }) + + describe("DELETE /admin/price-lists/:id/prices", () => { + it("should delete price list prices", async () => { + const priceSet = await createVariantPriceSet({ + container: appContainer, + variantId: variant.id, + prices: [], + }) + + const [priceList] = await pricingModule.createPriceLists([ + { + title: "test price list", + description: "test", + prices: [ + { + id: "test-price-id", + amount: 5000, + currency_code: "usd", + price_set_id: priceSet.id, + rules: { + region_id: region.id, + }, + }, + ], + }, + ]) + + let response = await api.delete( + `/admin/price-lists/${priceList.id}/prices`, + { ...adminHeaders, data: { ids: ["test-price-id"] } } + ) + + expect(response.status).toEqual(200) + expect(response.data).toEqual({ + ids: ["test-price-id"], + object: "price_list_prices", + deleted: true, + }) + }) + }) }) }, }) diff --git a/integration-tests/modules/__tests__/price-lists/admin/update-price-list.spec.ts b/integration-tests/modules/__tests__/price-lists/admin/update-price-list.spec.ts deleted file mode 100644 index aa02cc7796..0000000000 --- a/integration-tests/modules/__tests__/price-lists/admin/update-price-list.spec.ts +++ /dev/null @@ -1,265 +0,0 @@ -import { - simpleCustomerGroupFactory, - simpleProductFactory, - simpleRegionFactory, -} from "../../../../factories" - -import { - IPricingModuleService, - PriceListStatus, - PriceListType, -} from "@medusajs/types" -import adminSeeder from "../../../../helpers/admin-seeder" -import { createDefaultRuleTypes } from "../../../helpers/create-default-rule-types" -import { createVariantPriceSet } from "../../../helpers/create-variant-price-set" -import { medusaIntegrationTestRunner } from "medusa-test-utils" - -jest.setTimeout(50000) - -const adminHeaders = { - headers: { - "x-medusa-access-token": "test_token", - }, -} - -const env = { - MEDUSA_FF_MEDUSA_V2: true, -} - -medusaIntegrationTestRunner({ - env, - testSuite: ({ dbConnection, getContainer, api }) => { - describe.skip("POST /admin/price-lists/:id", () => { - let appContainer - let product - let variant - let variant2 - let pricingModuleService: IPricingModuleService - - beforeAll(async () => { - appContainer = getContainer() - pricingModuleService = appContainer.resolve("pricingModuleService") - }) - - beforeEach(async () => { - await adminSeeder(dbConnection) - await createDefaultRuleTypes(appContainer) - await simpleCustomerGroupFactory(dbConnection, { - id: "customer-group-2", - name: "Test Group 2", - }) - - await simpleRegionFactory(dbConnection, { - id: "test-region", - name: "Test Region", - currency_code: "usd", - tax_rate: 0, - }) - - product = await simpleProductFactory(dbConnection, { - id: "test-product-with-variant", - variants: [ - { - options: [{ option_id: "test-product-option-1", value: "test" }], - }, - { - options: [ - { option_id: "test-product-option-2", value: "test 2" }, - ], - }, - ], - options: [ - { - id: "test-product-option-1", - title: "Test option 1", - }, - { - id: "test-product-option-2", - title: "Test option 2", - }, - ], - }) - - variant = product.variants[0] - variant2 = product.variants[1] - }) - - it("should update price lists successfully with prices", async () => { - const var2PriceSet = await createVariantPriceSet({ - container: appContainer, - variantId: variant2.id, - prices: [], - }) - - const [priceList] = await pricingModuleService.createPriceLists([ - { - title: "test price list", - description: "test", - ends_at: new Date(), - starts_at: new Date(), - status: PriceListStatus.ACTIVE, - type: PriceListType.OVERRIDE, - prices: [ - { - amount: 3000, - currency_code: "usd", - price_set_id: var2PriceSet.id, - }, - ], - }, - ]) - - await createVariantPriceSet({ - container: appContainer, - variantId: variant.id, - prices: [ - { - amount: 3000, - currency_code: "usd", - }, - ], - }) - - const data = { - name: "new price list name", - description: "new price list description", - customer_groups: [{ id: "customer-group-2" }], - prices: [ - { - variant_id: variant.id, - amount: 5000, - currency_code: "usd", - }, - { - id: priceList?.price_set_money_amounts?.[0].money_amount?.id, - amount: 6000, - currency_code: "usd", - variant_id: variant2.id, - }, - ], - } - - await api.post(`admin/price-lists/${priceList.id}`, data, adminHeaders) - - const response = await api.get( - `/admin/price-lists/${priceList.id}`, - adminHeaders - ) - - expect(response.status).toEqual(200) - expect(response.data.price_list).toEqual( - expect.objectContaining({ - id: expect.any(String), - created_at: expect.any(String), - updated_at: expect.any(String), - deleted_at: null, - name: "new price list name", - description: "new price list description", - type: "override", - status: "active", - starts_at: expect.any(String), - ends_at: expect.any(String), - customer_groups: [ - { - id: expect.any(String), - created_at: expect.any(String), - updated_at: expect.any(String), - deleted_at: null, - name: "Test Group 2", - metadata: null, - }, - ], - prices: expect.arrayContaining([ - expect.objectContaining({ - id: expect.any(String), - created_at: expect.any(String), - updated_at: expect.any(String), - deleted_at: null, - currency_code: "usd", - amount: 5000, - min_quantity: null, - max_quantity: null, - price_list_id: priceList.id, - region_id: null, - variant: expect.objectContaining({ - id: variant.id, - }), - variant_id: variant.id, - }), - expect.objectContaining({ - id: expect.any(String), - created_at: expect.any(String), - updated_at: expect.any(String), - deleted_at: null, - currency_code: "usd", - amount: 6000, - min_quantity: null, - max_quantity: null, - price_list_id: priceList.id, - region_id: null, - variant: expect.objectContaining({ - id: variant2.id, - }), - variant_id: variant2.id, - }), - ]), - }) - ) - }) - - it("should not delete customer groups if customer_groups is not passed as a param", async () => { - await createVariantPriceSet({ - container: appContainer, - variantId: variant2.id, - prices: [], - }) - - const [priceList] = await pricingModuleService.createPriceLists([ - { - title: "test price list", - description: "test", - status: PriceListStatus.DRAFT, - rules: { - customer_group_id: ["customer-group-2"], - }, - prices: [], - }, - ]) - - await createVariantPriceSet({ - container: appContainer, - variantId: variant.id, - prices: [], - }) - - const data = { - status: PriceListStatus.ACTIVE, - } - - await api.post(`admin/price-lists/${priceList.id}`, data, adminHeaders) - - const response = await api.get( - `/admin/price-lists/${priceList.id}`, - adminHeaders - ) - - expect(response.status).toEqual(200) - expect(response.data.price_list).toEqual( - expect.objectContaining({ - id: expect.any(String), - customer_groups: [ - { - id: expect.any(String), - created_at: expect.any(String), - updated_at: expect.any(String), - deleted_at: null, - name: "Test Group 2", - metadata: null, - }, - ], - }) - ) - }) - }) - }, -}) diff --git a/packages/core-flows/src/definition/index.ts b/packages/core-flows/src/definition/index.ts index af9d0db6b6..4ebfb8c008 100644 --- a/packages/core-flows/src/definition/index.ts +++ b/packages/core-flows/src/definition/index.ts @@ -2,5 +2,4 @@ export * from "./cart" export * from "./inventory" export * from "./line-item" export * from "./payment-collection" -export * from "./price-list" export * from "./product" diff --git a/packages/core-flows/src/definition/price-list/create-price-lists.ts b/packages/core-flows/src/definition/price-list/create-price-lists.ts deleted file mode 100644 index 0ab242680a..0000000000 --- a/packages/core-flows/src/definition/price-list/create-price-lists.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { - TransactionStepsDefinition, - WorkflowManager, -} from "@medusajs/orchestration" -import { PricingTypes, WorkflowTypes } from "@medusajs/types" -import { exportWorkflow, pipe } from "@medusajs/workflows-sdk" - -import { Workflows } from "../../definitions" -import { PriceListHandlers } from "../../handlers" - -export enum CreatePriceListActions { - prepare = "prepare", - createPriceList = "createPriceList", -} - -const workflowSteps: TransactionStepsDefinition = { - next: { - action: CreatePriceListActions.prepare, - noCompensation: true, - next: { - action: CreatePriceListActions.createPriceList, - }, - }, -} - -const handlers = new Map([ - [ - CreatePriceListActions.prepare, - { - invoke: pipe( - { - inputAlias: CreatePriceListActions.prepare, - merge: true, - invoke: { - from: CreatePriceListActions.prepare, - }, - }, - PriceListHandlers.prepareCreatePriceLists - ), - }, - ], - [ - CreatePriceListActions.createPriceList, - { - invoke: pipe( - { - invoke: { - from: CreatePriceListActions.prepare, - alias: PriceListHandlers.createPriceLists.aliases.priceLists, - }, - }, - PriceListHandlers.createPriceLists - ), - compensate: pipe( - { - invoke: { - from: CreatePriceListActions.createPriceList, - alias: PriceListHandlers.removePriceLists.aliases.priceLists, - }, - }, - PriceListHandlers.removePriceLists - ), - }, - ], -]) - -WorkflowManager.register(Workflows.CreatePriceList, workflowSteps, handlers) - -export const createPriceLists = exportWorkflow< - WorkflowTypes.PriceListWorkflow.CreatePriceListWorkflowInputDTO, - { - priceList: PricingTypes.CreatePriceListDTO - }[] ->(Workflows.CreatePriceList, CreatePriceListActions.createPriceList) diff --git a/packages/core-flows/src/definition/price-list/remove-price-list-prices.ts b/packages/core-flows/src/definition/price-list/remove-price-list-prices.ts deleted file mode 100644 index 3bbe4f3d8b..0000000000 --- a/packages/core-flows/src/definition/price-list/remove-price-list-prices.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { - TransactionStepsDefinition, - WorkflowManager, -} from "@medusajs/orchestration" -import { WorkflowTypes } from "@medusajs/types" -import { exportWorkflow, pipe } from "@medusajs/workflows-sdk" - -import { Workflows } from "../../definitions" -import { PriceListHandlers } from "../../handlers" - -export enum RemovePriceListPricesActions { - prepare = "prepare", - removePriceListPrices = "removePriceListPrices", -} - -const workflowSteps: TransactionStepsDefinition = { - next: { - action: RemovePriceListPricesActions.prepare, - noCompensation: true, - next: { - action: RemovePriceListPricesActions.removePriceListPrices, - noCompensation: true, - }, - }, -} - -const handlers = new Map([ - [ - RemovePriceListPricesActions.prepare, - { - invoke: pipe( - { - inputAlias: RemovePriceListPricesActions.prepare, - merge: true, - invoke: { - from: RemovePriceListPricesActions.prepare, - }, - }, - PriceListHandlers.prepareRemovePriceListPrices - ), - }, - ], - [ - RemovePriceListPricesActions.removePriceListPrices, - { - invoke: pipe( - { - merge: true, - invoke: { - from: RemovePriceListPricesActions.prepare, - }, - }, - PriceListHandlers.removePrices - ), - }, - ], -]) - -WorkflowManager.register( - Workflows.RemovePriceListPrices, - workflowSteps, - handlers -) - -export const removePriceListPrices = exportWorkflow< - WorkflowTypes.PriceListWorkflow.RemovePriceListPricesWorkflowInputDTO, - string[] ->( - Workflows.RemovePriceListPrices, - RemovePriceListPricesActions.removePriceListPrices -) diff --git a/packages/core-flows/src/definition/price-list/remove-price-lists.ts b/packages/core-flows/src/definition/price-list/remove-price-lists.ts deleted file mode 100644 index dff32564ad..0000000000 --- a/packages/core-flows/src/definition/price-list/remove-price-lists.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { WorkflowTypes } from "@medusajs/types" -import { - TransactionStepsDefinition, - WorkflowManager, -} from "@medusajs/orchestration" -import { exportWorkflow, pipe } from "@medusajs/workflows-sdk" - -import { PriceListHandlers } from "../../handlers" -import { Workflows } from "../../definitions" - -export enum RemovePriceListActions { - removePriceList = "removePriceList", -} - -const workflowSteps: TransactionStepsDefinition = { - next: { - action: RemovePriceListActions.removePriceList, - noCompensation: true, - }, -} - -const handlers = new Map([ - [ - RemovePriceListActions.removePriceList, - { - invoke: pipe( - { - inputAlias: RemovePriceListActions.removePriceList, - merge: true, - invoke: { - from: RemovePriceListActions.removePriceList, - }, - }, - PriceListHandlers.removePriceLists - ), - }, - ], -]) - -WorkflowManager.register(Workflows.DeletePriceLists, workflowSteps, handlers) - -export const removePriceLists = exportWorkflow< - WorkflowTypes.PriceListWorkflow.RemovePriceListWorkflowInputDTO, - { - price_list_ids: string[] - } ->( - Workflows.DeletePriceLists, - RemovePriceListActions.removePriceList, - async (data) => { - return { - price_lists: data.price_lists.map((priceListId) => ({ - price_list: { id: priceListId }, - })), - } - } -) diff --git a/packages/core-flows/src/definition/price-list/remove-product-prices.ts b/packages/core-flows/src/definition/price-list/remove-product-prices.ts deleted file mode 100644 index 275dee5f6c..0000000000 --- a/packages/core-flows/src/definition/price-list/remove-product-prices.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { - TransactionStepsDefinition, - WorkflowManager, -} from "@medusajs/orchestration" -import { WorkflowTypes } from "@medusajs/types" -import { exportWorkflow, pipe } from "@medusajs/workflows-sdk" - -import { Workflows } from "../../definitions" -import { PriceListHandlers } from "../../handlers" - -export enum RemoveProductPricesActions { - prepare = "prepare", - removePriceListPriceSetPrices = "removePriceListPriceSetPrices", -} - -const workflowSteps: TransactionStepsDefinition = { - next: { - action: RemoveProductPricesActions.prepare, - noCompensation: true, - next: { - action: RemoveProductPricesActions.removePriceListPriceSetPrices, - noCompensation: true, - }, - }, -} - -const handlers = new Map([ - [ - RemoveProductPricesActions.prepare, - { - invoke: pipe( - { - inputAlias: RemoveProductPricesActions.prepare, - merge: true, - invoke: { - from: RemoveProductPricesActions.prepare, - }, - }, - PriceListHandlers.prepareRemoveProductPrices - ), - }, - ], - [ - RemoveProductPricesActions.removePriceListPriceSetPrices, - { - invoke: pipe( - { - merge: true, - invoke: { - from: RemoveProductPricesActions.prepare, - alias: PriceListHandlers.createPriceLists.aliases.priceLists, - }, - }, - PriceListHandlers.removePriceListPriceSetPrices - ), - }, - ], -]) - -WorkflowManager.register( - Workflows.RemovePriceListProductPrices, - workflowSteps, - handlers -) - -export const removePriceListProductPrices = exportWorkflow< - WorkflowTypes.PriceListWorkflow.RemovePriceListProductsWorkflowInputDTO, - string[] ->( - Workflows.RemovePriceListProductPrices, - RemoveProductPricesActions.removePriceListPriceSetPrices -) diff --git a/packages/core-flows/src/definition/price-list/remove-variant-prices.ts b/packages/core-flows/src/definition/price-list/remove-variant-prices.ts deleted file mode 100644 index a5e8bd2e17..0000000000 --- a/packages/core-flows/src/definition/price-list/remove-variant-prices.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { - TransactionStepsDefinition, - WorkflowManager, -} from "@medusajs/orchestration" -import { WorkflowTypes } from "@medusajs/types" -import { exportWorkflow, pipe } from "@medusajs/workflows-sdk" - -import { Workflows } from "../../definitions" -import { PriceListHandlers } from "../../handlers" - -export enum RemoveVariantPricesActions { - prepare = "prepare", - removePriceListPriceSetPrices = "removePriceListPriceSetPrices", -} - -const workflowSteps: TransactionStepsDefinition = { - next: { - action: RemoveVariantPricesActions.prepare, - noCompensation: true, - next: { - action: RemoveVariantPricesActions.removePriceListPriceSetPrices, - noCompensation: true, - }, - }, -} - -const handlers = new Map([ - [ - RemoveVariantPricesActions.prepare, - { - invoke: pipe( - { - inputAlias: RemoveVariantPricesActions.prepare, - merge: true, - invoke: { - from: RemoveVariantPricesActions.prepare, - }, - }, - PriceListHandlers.prepareRemoveVariantPrices - ), - }, - ], - [ - RemoveVariantPricesActions.removePriceListPriceSetPrices, - { - invoke: pipe( - { - merge: true, - invoke: { - from: RemoveVariantPricesActions.prepare, - alias: PriceListHandlers.createPriceLists.aliases.priceLists, - }, - }, - PriceListHandlers.removePriceListPriceSetPrices - ), - }, - ], -]) - -WorkflowManager.register( - Workflows.RemovePriceListVariantPrices, - workflowSteps, - handlers -) - -export const removePriceListVariantPrices = exportWorkflow< - WorkflowTypes.PriceListWorkflow.RemovePriceListVariantsWorkflowInputDTO, - string[] ->( - Workflows.RemovePriceListVariantPrices, - RemoveVariantPricesActions.removePriceListPriceSetPrices -) diff --git a/packages/core-flows/src/definition/price-list/update-price-lists.ts b/packages/core-flows/src/definition/price-list/update-price-lists.ts deleted file mode 100644 index 210ae242b3..0000000000 --- a/packages/core-flows/src/definition/price-list/update-price-lists.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { - TransactionStepsDefinition, - WorkflowManager, -} from "@medusajs/orchestration" -import { WorkflowTypes } from "@medusajs/types" -import { exportWorkflow, pipe } from "@medusajs/workflows-sdk" - -import { Workflows } from "../../definitions" -import { PriceListHandlers } from "../../handlers" - -export enum UpdatePriceListActions { - prepare = "prepare", - updatePriceList = "updatePriceList", -} - -const workflowSteps: TransactionStepsDefinition = { - action: UpdatePriceListActions.prepare, - noCompensation: true, - next: { - next: { - noCompensation: true, - action: UpdatePriceListActions.updatePriceList, - }, - }, -} - -const handlers = new Map([ - [ - UpdatePriceListActions.prepare, - { - invoke: pipe( - { - inputAlias: UpdatePriceListActions.prepare, - merge: true, - invoke: { - from: UpdatePriceListActions.prepare, - }, - }, - PriceListHandlers.prepareUpdatePriceLists - ), - }, - ], - [ - UpdatePriceListActions.updatePriceList, - { - invoke: pipe( - { - inputAlias: UpdatePriceListActions.updatePriceList, - merge: true, - invoke: { - from: UpdatePriceListActions.prepare, - }, - }, - PriceListHandlers.updatePriceLists - ), - }, - ], -]) - -WorkflowManager.register(Workflows.UpdatePriceLists, workflowSteps, handlers) - -export const updatePriceLists = exportWorkflow< - WorkflowTypes.PriceListWorkflow.UpdatePriceListWorkflowInputDTO, - { priceList: WorkflowTypes.PriceListWorkflow.UpdatePriceListWorkflowDTO }[] ->(Workflows.UpdatePriceLists, UpdatePriceListActions.updatePriceList) diff --git a/packages/core-flows/src/handlers/index.ts b/packages/core-flows/src/handlers/index.ts index 1a47ae2563..0e786bc666 100644 --- a/packages/core-flows/src/handlers/index.ts +++ b/packages/core-flows/src/handlers/index.ts @@ -3,7 +3,6 @@ export * as CommonHandlers from "./common" export * as CustomerHandlers from "./customer" export * as InventoryHandlers from "./inventory" export * as MiddlewaresHandlers from "./middlewares" -export * as PriceListHandlers from "./price-list" export * as ProductHandlers from "./product" export * as RegionHandlers from "./region" export * as SalesChannelHandlers from "./sales-channel" diff --git a/packages/core-flows/src/handlers/price-list/create-price-list.ts b/packages/core-flows/src/handlers/price-list/create-price-list.ts deleted file mode 100644 index b5715d28d3..0000000000 --- a/packages/core-flows/src/handlers/price-list/create-price-list.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { - CreatePriceListDTO, - IPricingModuleService, - PriceListDTO, -} from "@medusajs/types" - -import { ModuleRegistrationName } from "@medusajs/modules-sdk" -import { WorkflowArguments } from "@medusajs/workflows-sdk" - -type Result = { - priceList: PriceListDTO -}[] - -type Input = { - tag?: string - priceList: CreatePriceListDTO -}[] - -export async function createPriceLists({ - container, - data, -}: WorkflowArguments<{ - priceLists: Input -}>): Promise { - const pricingService: IPricingModuleService = container.resolve( - ModuleRegistrationName.PRICING - ) - - return await Promise.all( - data.priceLists.map(async (item) => { - const [priceList] = await pricingService!.createPriceLists([ - item.priceList, - ]) - - return { tag: item.tag ?? priceList.id, priceList } - }) - ) -} - -createPriceLists.aliases = { - priceLists: "priceLists", -} diff --git a/packages/core-flows/src/handlers/price-list/index.ts b/packages/core-flows/src/handlers/price-list/index.ts deleted file mode 100644 index 9f95fd8c6b..0000000000 --- a/packages/core-flows/src/handlers/price-list/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -export * from "./create-price-list" -export * from "./prepare-create-price-list" -export * from "./prepare-update-price-lists" -export * from "./remove-price-list" -export * from "./update-price-lists" -export * from "./prepare-remove-product-prices" -export * from "./remove-price-set-price-list-prices" -export * from "./prepare-remove-variant-prices" -export * from "./prepare-remove-price-list-prices" -export * from "./remove-prices" diff --git a/packages/core-flows/src/handlers/price-list/prepare-create-price-list.ts b/packages/core-flows/src/handlers/price-list/prepare-create-price-list.ts deleted file mode 100644 index 48538d6c76..0000000000 --- a/packages/core-flows/src/handlers/price-list/prepare-create-price-list.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { CreatePriceListDTO, PriceListWorkflow } from "@medusajs/types" -import { MedusaError } from "@medusajs/utils" -import { WorkflowArguments } from "@medusajs/workflows-sdk" - -type Result = { - tag?: string - priceList: CreatePriceListDTO -}[] - -export async function prepareCreatePriceLists({ - container, - data, -}: WorkflowArguments<{ - price_lists: (PriceListWorkflow.CreatePriceListWorkflowDTO & { - _associationTag?: string - })[] -}>): Promise { - const remoteQuery = container.resolve("remoteQuery") - const regionService = container.resolve("regionService") - - const { price_lists } = data - - const variantIds = price_lists - .map((priceList) => priceList.prices.map((price) => price.variant_id)) - .flat() - - const variables = { - variant_id: variantIds, - take: null, - } - - const query = { - product_variant_price_set: { - __args: variables, - fields: ["variant_id", "price_set_id"], - }, - } - - const variantPriceSets = await remoteQuery(query) - - const variantIdPriceSetIdMap: Map = new Map( - variantPriceSets.map((variantPriceSet) => [ - variantPriceSet.variant_id, - variantPriceSet.price_set_id, - ]) - ) - - const variantsWithoutPriceSets: string[] = [] - - for (const variantId of variantIds) { - if (!variantIdPriceSetIdMap.has(variantId)) { - variantsWithoutPriceSets.push(variantId) - } - } - - if (variantsWithoutPriceSets.length) { - throw new MedusaError( - MedusaError.Types.INVALID_DATA, - `No priceSet exist for variants: ${variantsWithoutPriceSets.join(", ")}` - ) - } - - const regionIds = price_lists - .map(({ prices }) => prices?.map((price) => price.region_id) ?? []) - .flat(2) - const regions = await regionService.list({ id: regionIds }, {}) - const regionIdCurrencyCodeMap: Map = new Map( - regions.map((region: { id: string; currency_code: string }) => [ - region.id, - region.currency_code, - ]) - ) - - return price_lists.map((priceListDTO) => { - priceListDTO.title ??= priceListDTO.name - const { _associationTag, name, prices, ...rest } = priceListDTO - - const priceList = rest as CreatePriceListDTO - - priceList.rules ??= {} - priceList.prices = - prices?.map((price) => { - const price_set_id = variantIdPriceSetIdMap.get(price.variant_id)! - - const rules: Record = {} - if (price.region_id) { - rules.region_id = price.region_id - } - - return { - currency_code: - regionIdCurrencyCodeMap.get(price.region_id as string) ?? - (price.currency_code as string), - amount: price.amount, - min_quantity: price.min_quantity, - max_quantity: price.max_quantity, - price_set_id, - rules, - } - }) ?? [] - - return { priceList, tag: _associationTag } - }) -} - -prepareCreatePriceLists.aliases = { - payload: "payload", -} diff --git a/packages/core-flows/src/handlers/price-list/prepare-remove-price-list-prices.ts b/packages/core-flows/src/handlers/price-list/prepare-remove-price-list-prices.ts deleted file mode 100644 index 649e7d5a22..0000000000 --- a/packages/core-flows/src/handlers/price-list/prepare-remove-price-list-prices.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { ModuleRegistrationName } from "@medusajs/modules-sdk" -import { IPricingModuleService } from "@medusajs/types" -import { WorkflowArguments } from "@medusajs/workflows-sdk" - -type Result = { - moneyAmountIds: string[] - priceListId: string -} - -export async function prepareRemovePriceListPrices({ - container, - data, -}: WorkflowArguments<{ - money_amount_ids: string[] - price_list_id: string -}>): Promise { - const pricingService: IPricingModuleService = container.resolve( - ModuleRegistrationName.PRICING - ) - - const { - price_list_id: priceListId, - money_amount_ids: moneyAmountIdsToDelete, - } = data - - const moneyAmounts = await pricingService.listMoneyAmounts( - { id: moneyAmountIdsToDelete }, - { - relations: [ - "price_set_money_amount", - "price_set_money_amount.price_list", - ], - take: null, - } - ) - - const moneyAmountIds = moneyAmounts - .filter( - (moneyAmount) => - moneyAmount?.price_set_money_amount?.price_list?.id === priceListId - ) - .map((ma) => ma.id) - - return { moneyAmountIds, priceListId } -} - -prepareRemovePriceListPrices.aliases = { - payload: "payload", -} diff --git a/packages/core-flows/src/handlers/price-list/prepare-remove-product-prices.ts b/packages/core-flows/src/handlers/price-list/prepare-remove-product-prices.ts deleted file mode 100644 index e3cb9a376f..0000000000 --- a/packages/core-flows/src/handlers/price-list/prepare-remove-product-prices.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { WorkflowArguments } from "@medusajs/workflows-sdk" -import { prepareCreatePriceLists } from "./prepare-create-price-list" - -type Result = { - priceSetIds: string[] - priceListId: string -} - -export async function prepareRemoveProductPrices({ - container, - data, -}: WorkflowArguments<{ - product_ids: string[] - price_list_id: string -}>): Promise { - const remoteQuery = container.resolve("remoteQuery") - - const { price_list_id, product_ids } = data - - const variables = { - id: product_ids, - take: null, - } - - const query = { - product: { - __args: variables, - ...defaultAdminProductRemoteQueryObject, - }, - } - - const productsWithVariantPriceSets: QueryResult[] = await remoteQuery(query) - - const priceSetIds = productsWithVariantPriceSets - .map(({ variants }) => variants.map(({ price }) => price.price_set_id)) - .flat() - - return { priceSetIds, priceListId: price_list_id } -} - -prepareCreatePriceLists.aliases = { - payload: "payload", -} - -type QueryResult = { - id: string - variants: { - id: string - price: { - price_set_id: string - variant_id: string - } - }[] -} - -const defaultAdminProductRemoteQueryObject = { - fields: ["id"], - variants: { - price: { - fields: ["variant_id", "price_set_id"], - }, - }, -} diff --git a/packages/core-flows/src/handlers/price-list/prepare-remove-variant-prices.ts b/packages/core-flows/src/handlers/price-list/prepare-remove-variant-prices.ts deleted file mode 100644 index 9580980aad..0000000000 --- a/packages/core-flows/src/handlers/price-list/prepare-remove-variant-prices.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { WorkflowArguments } from "@medusajs/workflows-sdk" -import { prepareCreatePriceLists } from "./prepare-create-price-list" - -type Result = { - priceSetIds: string[] - priceListId: string -} - -export async function prepareRemoveVariantPrices({ - container, - data, -}: WorkflowArguments<{ - variant_ids: string[] - price_list_id: string -}>): Promise { - const remoteQuery = container.resolve("remoteQuery") - - const { price_list_id, variant_ids } = data - - const variables = { - variant_id: variant_ids, - take: null, - } - - const query = { - product_variant_price_set: { - __args: variables, - fields: ["variant_id", "price_set_id"], - }, - } - - const productsWithVariantPriceSets: QueryResult[] = await remoteQuery(query) - - const priceSetIds = productsWithVariantPriceSets.map( - (variantPriceSet) => variantPriceSet.price_set_id - ) - - return { priceSetIds, priceListId: price_list_id } -} - -prepareCreatePriceLists.aliases = { - payload: "payload", -} - -type QueryResult = { - price_set_id: string - variant_id: string -} - -const defaultAdminProductRemoteQueryObject = { - fields: ["id"], - variants: { - price: { - fields: ["variant_id", "price_set_id"], - }, - }, -} diff --git a/packages/core-flows/src/handlers/price-list/prepare-update-price-lists.ts b/packages/core-flows/src/handlers/price-list/prepare-update-price-lists.ts deleted file mode 100644 index 555539c314..0000000000 --- a/packages/core-flows/src/handlers/price-list/prepare-update-price-lists.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { - PriceListPriceDTO, - UpdatePriceListDTO, - WorkflowTypes, -} from "@medusajs/types" -import { WorkflowArguments } from "@medusajs/workflows-sdk" - -type Result = { - priceLists: UpdatePriceListDTO[] - priceListPricesMap: Map -} - -export async function prepareUpdatePriceLists({ - container, - data, -}: WorkflowArguments<{ - price_lists: WorkflowTypes.PriceListWorkflow.UpdatePriceListWorkflowDTO[] -}>): Promise { - const { price_lists: priceListsData } = data - const remoteQuery = container.resolve("remoteQuery") - const regionService = container.resolve("regionService") - - const variantPriceSetMap = new Map() - const priceListPricesMap = new Map() - - const variantIds = priceListsData - .map((priceListData) => priceListData.prices?.map((p) => p.variant_id)) - .flat() - - const variables = { - variant_id: variantIds, - take: null, - } - - const query = { - product_variant_price_set: { - __args: variables, - fields: ["variant_id", "price_set_id"], - }, - } - - const variantPriceSets = await remoteQuery(query) - - for (const { variant_id, price_set_id } of variantPriceSets) { - variantPriceSetMap.set(variant_id, price_set_id) - } - - const regionIds = priceListsData - .map(({ prices }) => prices?.map((price) => price.region_id) ?? []) - .flat(2) - const regions = await regionService.list({ id: regionIds }) - const regionsMap: Map = new Map( - regions.map((region: { id: string; currency_code: string }) => [ - region.id, - region.currency_code, - ]) - ) - - const priceLists = priceListsData.map((priceListData) => { - const priceListPrices: PriceListPriceDTO[] = [] - - priceListData.prices?.forEach((price) => { - const { variant_id, ...priceData } = price - if (!variant_id) { - return - } - - const rules: Record = {} - if (price.region_id) { - rules.region_id = price.region_id - } - - priceListPrices.push({ - id: priceData.id, - price_set_id: variantPriceSetMap.get(variant_id) as string, - currency_code: - regionsMap.get(priceData.region_id as string) ?? - (priceData.currency_code as string), - amount: priceData.amount, - min_quantity: priceData.min_quantity, - max_quantity: priceData.max_quantity, - rules, - }) - - return - }) - - priceListPricesMap.set(priceListData.id, priceListPrices) - - delete priceListData?.prices - - const priceListDataClone: UpdatePriceListDTO = { - ...priceListData, - } - - if (priceListData.name) { - priceListDataClone.title = priceListData.name - } - - return priceListDataClone - }) - - return { priceLists, priceListPricesMap } -} - -prepareUpdatePriceLists.aliases = { - payload: "prepare", -} diff --git a/packages/core-flows/src/handlers/price-list/remove-price-list.ts b/packages/core-flows/src/handlers/price-list/remove-price-list.ts deleted file mode 100644 index 36ffa03d8c..0000000000 --- a/packages/core-flows/src/handlers/price-list/remove-price-list.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { IPricingModuleService, PriceListDTO } from "@medusajs/types" - -import { ModuleRegistrationName } from "@medusajs/modules-sdk" -import { WorkflowArguments } from "@medusajs/workflows-sdk" - -export async function removePriceLists({ - container, - data, -}: WorkflowArguments<{ - price_lists: { - price_list: PriceListDTO - }[] -}>): Promise< - { - price_list: PriceListDTO - }[] -> { - const pricingService: IPricingModuleService = container.resolve( - ModuleRegistrationName.PRICING - ) - - await pricingService!.deletePriceLists( - data.price_lists.map(({ price_list }) => price_list.id) - ) - - return data.price_lists -} - -removePriceLists.aliases = { - priceLists: "priceLists", -} diff --git a/packages/core-flows/src/handlers/price-list/remove-price-set-price-list-prices.ts b/packages/core-flows/src/handlers/price-list/remove-price-set-price-list-prices.ts deleted file mode 100644 index 6e4a5d0d71..0000000000 --- a/packages/core-flows/src/handlers/price-list/remove-price-set-price-list-prices.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { ModuleRegistrationName } from "@medusajs/modules-sdk" -import { IPricingModuleService } from "@medusajs/types" -import { WorkflowArguments } from "@medusajs/workflows-sdk" -import { prepareCreatePriceLists } from "./prepare-create-price-list" - -export async function removePriceListPriceSetPrices({ - container, - data, -}: WorkflowArguments<{ - priceSetIds: string[] - priceListId: string -}>): Promise { - const { priceSetIds, priceListId } = data - const pricingService: IPricingModuleService = container.resolve( - ModuleRegistrationName.PRICING - ) - - const priceSetMoneyAmounts = await pricingService.listPriceSetMoneyAmounts( - { - price_set_id: priceSetIds, - price_list_id: [priceListId], - }, - { - relations: ["money_amount"], - take: null, - } - ) - - const moneyAmountIDs = priceSetMoneyAmounts - .map((priceSetMoneyAmount) => priceSetMoneyAmount.money_amount?.id) - .filter((moneyAmountId): moneyAmountId is string => !!moneyAmountId) - - await pricingService.deleteMoneyAmounts(moneyAmountIDs) - - return moneyAmountIDs -} - -prepareCreatePriceLists.aliases = { - payload: "payload", -} diff --git a/packages/core-flows/src/handlers/price-list/remove-prices.ts b/packages/core-flows/src/handlers/price-list/remove-prices.ts deleted file mode 100644 index 502452e126..0000000000 --- a/packages/core-flows/src/handlers/price-list/remove-prices.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { ModuleRegistrationName } from "@medusajs/modules-sdk" -import { IPricingModuleService } from "@medusajs/types" -import { WorkflowArguments } from "@medusajs/workflows-sdk" - -type Result = { - deletedPriceIds: string[] -} - -export async function removePrices({ - container, - data, -}: WorkflowArguments<{ - moneyAmountIds: string[] -}>): Promise { - const { moneyAmountIds } = data - const pricingService: IPricingModuleService = container.resolve( - ModuleRegistrationName.PRICING - ) - - await pricingService.deleteMoneyAmounts(moneyAmountIds) - - return { - deletedPriceIds: moneyAmountIds, - } -} - -removePrices.aliases = { - payload: "payload", -} diff --git a/packages/core-flows/src/handlers/price-list/update-price-lists.ts b/packages/core-flows/src/handlers/price-list/update-price-lists.ts deleted file mode 100644 index fc99a167f0..0000000000 --- a/packages/core-flows/src/handlers/price-list/update-price-lists.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { - AddPriceListPricesDTO, - IPricingModuleService, - PriceListDTO, - PriceListPriceDTO, - UpdateMoneyAmountDTO, - UpdatePriceListDTO, -} from "@medusajs/types" - -import { ModuleRegistrationName } from "@medusajs/modules-sdk" -import { WorkflowArguments } from "@medusajs/workflows-sdk" - -type Result = { - priceLists: PriceListDTO[] -} - -export async function updatePriceLists({ - container, - data, -}: WorkflowArguments<{ - priceLists: UpdatePriceListDTO[] - priceListPricesMap: Map -}>): Promise { - const { priceLists: priceListsData, priceListPricesMap } = data - const pricingService: IPricingModuleService = container.resolve( - ModuleRegistrationName.PRICING - ) - - const priceLists = await pricingService.updatePriceLists(priceListsData) - const addPriceListPricesData: AddPriceListPricesDTO[] = [] - const moneyAmountsToUpdate: UpdateMoneyAmountDTO[] = [] - - for (const [priceListId, prices] of priceListPricesMap.entries()) { - const moneyAmountsToCreate: PriceListPriceDTO[] = [] - - for (const price of prices) { - if (price.id) { - moneyAmountsToUpdate.push(price as UpdateMoneyAmountDTO) - } else { - moneyAmountsToCreate.push(price) - } - } - - addPriceListPricesData.push({ - priceListId, - prices: moneyAmountsToCreate, - }) - } - - if (addPriceListPricesData.length) { - await pricingService.addPriceListPrices(addPriceListPricesData) - } - - if (moneyAmountsToUpdate.length) { - await pricingService.updateMoneyAmounts(moneyAmountsToUpdate) - } - - return { priceLists } -} - -updatePriceLists.aliases = { - payload: "updatePriceLists", -} diff --git a/packages/core-flows/src/index.ts b/packages/core-flows/src/index.ts index f1ea06b120..0b9e59d627 100644 --- a/packages/core-flows/src/index.ts +++ b/packages/core-flows/src/index.ts @@ -8,6 +8,7 @@ export * from "./fulfillment" export * as Handlers from "./handlers" export * from "./invite" export * from "./payment" +export * from "./price-list" export * from "./pricing" export * from "./product" export * from "./promotion" diff --git a/packages/core-flows/src/price-list/index.ts b/packages/core-flows/src/price-list/index.ts new file mode 100644 index 0000000000..68de82c9f9 --- /dev/null +++ b/packages/core-flows/src/price-list/index.ts @@ -0,0 +1,2 @@ +export * from "./steps" +export * from "./workflows" diff --git a/packages/core-flows/src/price-list/steps/create-price-lists.ts b/packages/core-flows/src/price-list/steps/create-price-lists.ts new file mode 100644 index 0000000000..dadd198d17 --- /dev/null +++ b/packages/core-flows/src/price-list/steps/create-price-lists.ts @@ -0,0 +1,58 @@ +import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { + CreatePriceListDTO, + CreatePriceListWorkflowInputDTO, + IPricingModuleService, +} from "@medusajs/types" +import { StepResponse, createStep } from "@medusajs/workflows-sdk" + +type WorkflowStepInput = { + data: CreatePriceListWorkflowInputDTO[] + variant_price_map: Record +} + +export const createPriceListsStepId = "create-price-lists" +export const createPriceListsStep = createStep( + createPriceListsStepId, + async (stepInput: WorkflowStepInput, { container }) => { + const { data, variant_price_map: variantPriceMap } = stepInput + + const pricingModule = container.resolve( + ModuleRegistrationName.PRICING + ) + + const createData = data.map((priceListDTO) => { + const { prices = [], ...rest } = priceListDTO + const createPriceListData: CreatePriceListDTO = { ...rest } + + createPriceListData.prices = prices.map((price) => ({ + currency_code: price.currency_code, + amount: price.amount, + min_quantity: price.min_quantity, + max_quantity: price.max_quantity, + price_set_id: variantPriceMap[price.variant_id], + rules: price.rules, + })) + + return createPriceListData + }) + + const createdPriceLists = await pricingModule.createPriceLists(createData) + + return new StepResponse( + createdPriceLists, + createdPriceLists.map((createdPriceLists) => createdPriceLists.id) + ) + }, + async (createdPriceListIds, { container }) => { + if (!createdPriceListIds?.length) { + return + } + + const pricingModule = container.resolve( + ModuleRegistrationName.PRICING + ) + + await pricingModule.deletePriceLists(createdPriceListIds) + } +) diff --git a/packages/core-flows/src/price-list/steps/delete-price-lists.ts b/packages/core-flows/src/price-list/steps/delete-price-lists.ts new file mode 100644 index 0000000000..f91974e6ec --- /dev/null +++ b/packages/core-flows/src/price-list/steps/delete-price-lists.ts @@ -0,0 +1,30 @@ +import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { IPricingModuleService } from "@medusajs/types" +import { StepResponse, createStep } from "@medusajs/workflows-sdk" + +export const deletePriceListsStepId = "delete-campaigns" +export const deletePriceListsStep = createStep( + deletePriceListsStepId, + async (ids: string[], { container }) => { + const pricingModule = container.resolve( + ModuleRegistrationName.PRICING + ) + + // TODO: Implement soft delete price lists + await pricingModule.deletePriceLists(ids) + + return new StepResponse(void 0, ids) + }, + async (idsToRestore, { container }) => { + if (!idsToRestore?.length) { + return + } + + const pricingModule = container.resolve( + ModuleRegistrationName.PRICING + ) + + // TODO: Implement restore price lists + // await pricingModule.restorePriceLists(idsToRestore) + } +) diff --git a/packages/core-flows/src/price-list/steps/index.ts b/packages/core-flows/src/price-list/steps/index.ts new file mode 100644 index 0000000000..fcef10dc22 --- /dev/null +++ b/packages/core-flows/src/price-list/steps/index.ts @@ -0,0 +1,7 @@ +export * from "./create-price-lists" +export * from "./delete-price-lists" +export * from "./remove-price-list-prices" +export * from "./update-price-lists" +export * from "./upsert-price-list-prices" +export * from "./validate-price-lists" +export * from "./validate-variant-price-links" diff --git a/packages/core-flows/src/price-list/steps/remove-price-list-prices.ts b/packages/core-flows/src/price-list/steps/remove-price-list-prices.ts new file mode 100644 index 0000000000..c441cea082 --- /dev/null +++ b/packages/core-flows/src/price-list/steps/remove-price-list-prices.ts @@ -0,0 +1,41 @@ +import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { IPricingModuleService } from "@medusajs/types" +import { StepResponse, createStep } from "@medusajs/workflows-sdk" + +export const removePriceListPricesStepId = "remove-price-list-prices" +export const removePriceListPricesStep = createStep( + removePriceListPricesStepId, + async (ids: string[], { container }) => { + if (!ids.length) { + return new StepResponse(null, []) + } + + const pricingModule = container.resolve( + ModuleRegistrationName.PRICING + ) + + const psmas = await pricingModule.listPriceSetMoneyAmounts( + { id: ids }, + { relations: ["price_list"] } + ) + + await pricingModule.removePrices(psmas.map((psma) => psma.id)) + + return new StepResponse( + null, + psmas.map((psma) => psma.id) + ) + }, + async (ids, { container }) => { + if (!ids) { + return + } + + const pricingModule = container.resolve( + ModuleRegistrationName.PRICING + ) + + // TODO: This needs to be implemented + // pricingModule.restorePrices(ids) + } +) diff --git a/packages/core-flows/src/price-list/steps/update-price-lists.ts b/packages/core-flows/src/price-list/steps/update-price-lists.ts new file mode 100644 index 0000000000..9c3dc1e01b --- /dev/null +++ b/packages/core-flows/src/price-list/steps/update-price-lists.ts @@ -0,0 +1,105 @@ +import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { + IPricingModuleService, + UpdatePriceListDTO, + UpdatePriceListWorkflowInputDTO, +} from "@medusajs/types" +import { + buildPriceListRules, + convertItemResponseToUpdateRequest, + getSelectsAndRelationsFromObjectArray, +} from "@medusajs/utils" +import { StepResponse, createStep } from "@medusajs/workflows-sdk" + +export const updatePriceListsStepId = "update-price-lists" +export const updatePriceListsStep = createStep( + updatePriceListsStepId, + async (data: UpdatePriceListDTO[], { container }) => { + const pricingModule = container.resolve( + ModuleRegistrationName.PRICING + ) + + const { dataBeforeUpdate, selects, relations } = await getDataBeforeUpdate( + pricingModule, + data + ) + + const updatedPriceLists = await pricingModule.updatePriceLists(data) + + return new StepResponse(updatedPriceLists, { + dataBeforeUpdate, + selects, + relations, + }) + }, + async (revertInput, { container }) => { + if (!revertInput) { + return + } + + const { dataBeforeUpdate, selects, relations } = revertInput + const pricingModule = container.resolve( + ModuleRegistrationName.PRICING + ) + + await pricingModule.updatePriceLists( + dataBeforeUpdate.map((data) => { + const { price_list_rules: priceListRules = [], rules, ...rest } = data + + const updateData: UpdatePriceListDTO = { + ...rest, + rules: buildPriceListRules(priceListRules), + } + + return convertItemResponseToUpdateRequest( + updateData, + selects, + relations + ) + }) + ) + } +) + +// Since rules is an API level abstraction, we need to do this dance of data fetching +// to its actual attributes in the module to do perform a revert in case a rollback needs to happen. +// TODO: Check if there is a better way to approach this. Preferably the module should be handling this +// if this is not the response the module provides. +async function getDataBeforeUpdate( + pricingModule: IPricingModuleService, + data: UpdatePriceListWorkflowInputDTO[] +) { + const { selects, relations } = getSelectsAndRelationsFromObjectArray(data, { + objectFields: ["rules"], + }) + const selectsClone = [...selects] + const relationsClone = [...relations] + + if (selectsClone.includes("rules")) { + const index = selectsClone.indexOf("rules", 0) + + if (index > -1) { + selectsClone.splice(index, 1) + } + + selectsClone.push( + "price_list_rules.price_list_rule_values.value", + "price_list_rules.rule_type.rule_attribute" + ) + relationsClone.push( + "price_list_rules.price_list_rule_values", + "price_list_rules.rule_type" + ) + } + + const dataBeforeUpdate = await pricingModule.listPriceLists( + { id: data.map((d) => d.id) }, + { relations: relationsClone, select: selectsClone } + ) + + return { + dataBeforeUpdate, + selects, + relations, + } +} diff --git a/packages/core-flows/src/price-list/steps/upsert-price-list-prices.ts b/packages/core-flows/src/price-list/steps/upsert-price-list-prices.ts new file mode 100644 index 0000000000..5204c953dd --- /dev/null +++ b/packages/core-flows/src/price-list/steps/upsert-price-list-prices.ts @@ -0,0 +1,131 @@ +import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { + AddPriceListPricesDTO, + CreatePriceListPriceDTO, + CreatePriceListPriceWorkflowDTO, + IPricingModuleService, + PriceSetMoneyAmountDTO, + UpdatePriceListPriceDTO, + UpdatePriceListPriceWorkflowDTO, + UpdatePriceListPricesDTO, + UpdatePriceListWorkflowInputDTO, +} from "@medusajs/types" +import { buildPriceSetPricesForModule, promiseAll } from "@medusajs/utils" +import { StepResponse, createStep } from "@medusajs/workflows-sdk" + +type WorkflowStepInput = { + data: Pick[] + variant_price_map: Record +} + +export const upsertPriceListPricesStepId = "upsert-price-list-prices" +export const upsertPriceListPricesStep = createStep( + upsertPriceListPricesStepId, + async (stepInput: WorkflowStepInput, { container }) => { + const { data, variant_price_map: variantPriceSetMap } = stepInput + + const priceListPricesToUpdate: UpdatePriceListPricesDTO[] = [] + const priceListPricesToAdd: AddPriceListPricesDTO[] = [] + const pricingModule = container.resolve( + ModuleRegistrationName.PRICING + ) + + for (const upsertPriceListPricesData of data) { + const { prices = [], id } = upsertPriceListPricesData + + const pricesToAdd: CreatePriceListPriceDTO[] = [] + const pricesToUpdate: UpdatePriceListPriceDTO[] = [] + + for (const price of prices) { + const priceSetId = variantPriceSetMap[price.variant_id!] + + if (isPriceUpdate(price)) { + pricesToUpdate.push({ ...price, price_set_id: priceSetId }) + } else { + pricesToAdd.push({ ...price, price_set_id: priceSetId }) + } + } + + if (pricesToUpdate.length) { + priceListPricesToUpdate.push({ + price_list_id: id, + prices: pricesToUpdate, + }) + } + + if (pricesToAdd.length) { + priceListPricesToAdd.push({ + price_list_id: id, + prices: pricesToAdd, + }) + } + } + + const updatedPriceSetMoneyAmounts = + await pricingModule.listPriceSetMoneyAmounts( + { + id: priceListPricesToUpdate + .map((priceListData) => + priceListData.prices.map((price) => price.id) + ) + .filter(Boolean) + .flat(1), + }, + { relations: ["price_list"] } + ) + + const priceListPsmaMap = new Map() + const dataBeforePriceUpdate: UpdatePriceListPricesDTO[] = [] + + for (const priceSetMoneyAmount of updatedPriceSetMoneyAmounts) { + const priceListId = priceSetMoneyAmount.price_list!.id + const psmas = priceListPsmaMap.get(priceListId) || [] + + priceListPsmaMap.set(priceListId, psmas) + } + + for (const [priceListId, psmas] of Object.entries(priceListPsmaMap)) { + dataBeforePriceUpdate.push({ + price_list_id: priceListId, + prices: buildPriceSetPricesForModule(psmas), + }) + } + + // TODO: `addPriceListPrices` will return a list of price lists + // This should be reworked to return prices instead, as we need to + // do a revert incase this step fails + const [createdPriceListPrices, _] = await promiseAll([ + pricingModule.addPriceListPrices(priceListPricesToAdd), + pricingModule.updatePriceListPrices(priceListPricesToUpdate), + ]) + + return new StepResponse(null, { + createdPriceListPrices, + updatedPriceListPrices: dataBeforePriceUpdate, + }) + }, + async (data, { container }) => { + if (!data) { + return + } + + const { createdPriceListPrices = [], updatedPriceListPrices = [] } = data + const pricingModule = container.resolve( + ModuleRegistrationName.PRICING + ) + + if (createdPriceListPrices.length) { + await pricingModule.removePrices(createdPriceListPrices.map((p) => p.id)) + } + + if (updatedPriceListPrices.length) { + await pricingModule.updatePriceListPrices(updatedPriceListPrices) + } + } +) + +function isPriceUpdate( + data: UpdatePriceListPriceWorkflowDTO | CreatePriceListPriceWorkflowDTO +): data is UpdatePriceListPriceWorkflowDTO { + return "id" in data +} diff --git a/packages/core-flows/src/price-list/steps/validate-price-lists.ts b/packages/core-flows/src/price-list/steps/validate-price-lists.ts new file mode 100644 index 0000000000..c3474d4299 --- /dev/null +++ b/packages/core-flows/src/price-list/steps/validate-price-lists.ts @@ -0,0 +1,41 @@ +import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { + IPricingModuleService, + PriceListDTO, + UpdatePriceListDTO, +} from "@medusajs/types" +import { MedusaError, arrayDifference } from "@medusajs/utils" +import { StepResponse, createStep } from "@medusajs/workflows-sdk" + +export const validatePriceListsStepId = "validate-price-lists" +export const validatePriceListsStep = createStep( + validatePriceListsStepId, + async (data: Pick[], { container }) => { + const pricingModule = container.resolve( + ModuleRegistrationName.PRICING + ) + + const priceListIds = data.map((d) => d.id) + const priceLists = await pricingModule.listPriceLists({ id: priceListIds }) + + const diff = arrayDifference( + priceListIds, + priceLists.map((pl) => pl.id) + ) + + if (diff.length) { + throw new MedusaError( + MedusaError.Types.NOT_FOUND, + `Price lists with id: ${diff.join(", ")} was not found` + ) + } + + const priceListMap: Record = {} + + for (const priceList of priceLists) { + priceListMap[priceList.id] = priceList + } + + return new StepResponse(priceListMap) + } +) diff --git a/packages/core-flows/src/price-list/steps/validate-variant-price-links.ts b/packages/core-flows/src/price-list/steps/validate-variant-price-links.ts new file mode 100644 index 0000000000..9178beb7a9 --- /dev/null +++ b/packages/core-flows/src/price-list/steps/validate-variant-price-links.ts @@ -0,0 +1,48 @@ +import { UpdatePriceListWorkflowInputDTO } from "@medusajs/types" +import { + ContainerRegistrationKeys, + MedusaError, + remoteQueryObjectFromString, +} from "@medusajs/utils" +import { StepResponse, createStep } from "@medusajs/workflows-sdk" + +type WorkflowStepInput = Pick[] + +export const validateVariantPriceLinksStepId = "validate-variant-price-links" +export const validateVariantPriceLinksStep = createStep( + validateVariantPriceLinksStepId, + async (data: WorkflowStepInput, { container }) => { + const remoteQuery = container.resolve( + ContainerRegistrationKeys.REMOTE_QUERY + ) + + const variantIds: string[] = data + .map((pl) => pl?.prices?.map((price) => price.variant_id!) || []) + .filter(Boolean) + .flat(1) + + const variantPricingLinkQuery = remoteQueryObjectFromString({ + entryPoint: "product_variant_price_set", + fields: ["variant_id", "price_set_id"], + variables: { variant_id: variantIds, take: null }, + }) + + const links = await remoteQuery(variantPricingLinkQuery) + const variantPriceSetMap: Record = {} + + for (const link of links) { + variantPriceSetMap[link.variant_id] = link.price_set_id + } + + const withoutLinks = variantIds.filter((id) => !variantPriceSetMap[id]) + + if (withoutLinks.length) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `No price set exist for variants: ${withoutLinks.join(", ")}` + ) + } + + return new StepResponse(variantPriceSetMap) + } +) diff --git a/packages/core-flows/src/price-list/workflows/create-price-lists.ts b/packages/core-flows/src/price-list/workflows/create-price-lists.ts new file mode 100644 index 0000000000..f45d382122 --- /dev/null +++ b/packages/core-flows/src/price-list/workflows/create-price-lists.ts @@ -0,0 +1,20 @@ +import { CreatePriceListWorkflowInputDTO, PriceListDTO } from "@medusajs/types" +import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk" +import { createPriceListsStep, validateVariantPriceLinksStep } from "../steps" + +type WorkflowInput = { price_lists_data: CreatePriceListWorkflowInputDTO[] } + +export const createPriceListsWorkflowId = "create-price-lists" +export const createPriceListsWorkflow = createWorkflow( + createPriceListsWorkflowId, + (input: WorkflowData): WorkflowData => { + const variantPriceMap = validateVariantPriceLinksStep( + input.price_lists_data + ) + + return createPriceListsStep({ + data: input.price_lists_data, + variant_price_map: variantPriceMap, + }) + } +) diff --git a/packages/core-flows/src/price-list/workflows/delete-price-lists.ts b/packages/core-flows/src/price-list/workflows/delete-price-lists.ts new file mode 100644 index 0000000000..70d03a1871 --- /dev/null +++ b/packages/core-flows/src/price-list/workflows/delete-price-lists.ts @@ -0,0 +1,12 @@ +import { createWorkflow, WorkflowData } from "@medusajs/workflows-sdk" +import { deletePriceListsStep } from "../steps" + +type WorkflowInput = { ids: string[] } + +export const deletePriceListsWorkflowId = "delete-price-lists" +export const deletePriceListsWorkflow = createWorkflow( + deletePriceListsWorkflowId, + (input: WorkflowData): WorkflowData => { + return deletePriceListsStep(input.ids) + } +) diff --git a/packages/core-flows/src/definition/price-list/index.ts b/packages/core-flows/src/price-list/workflows/index.ts similarity index 50% rename from packages/core-flows/src/definition/price-list/index.ts rename to packages/core-flows/src/price-list/workflows/index.ts index 143f796859..479dc8368d 100644 --- a/packages/core-flows/src/definition/price-list/index.ts +++ b/packages/core-flows/src/price-list/workflows/index.ts @@ -1,6 +1,5 @@ export * from "./create-price-lists" -export * from "./remove-price-lists" +export * from "./delete-price-lists" export * from "./remove-price-list-prices" -export * from "./remove-product-prices" -export * from "./remove-variant-prices" export * from "./update-price-lists" +export * from "./upsert-price-list-prices" diff --git a/packages/core-flows/src/price-list/workflows/remove-price-list-prices.ts b/packages/core-flows/src/price-list/workflows/remove-price-list-prices.ts new file mode 100644 index 0000000000..c048e9abff --- /dev/null +++ b/packages/core-flows/src/price-list/workflows/remove-price-list-prices.ts @@ -0,0 +1,12 @@ +import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk" +import { removePriceListPricesStep } from "../steps" + +type WorkflowInput = { ids: string[] } + +export const removePriceListPricesWorkflowId = "remove-price-list-prices" +export const removePriceListPricesWorkflow = createWorkflow( + removePriceListPricesWorkflowId, + (input: WorkflowData): WorkflowData => { + removePriceListPricesStep(input.ids) + } +) diff --git a/packages/core-flows/src/price-list/workflows/update-price-lists.ts b/packages/core-flows/src/price-list/workflows/update-price-lists.ts new file mode 100644 index 0000000000..62453d8bf1 --- /dev/null +++ b/packages/core-flows/src/price-list/workflows/update-price-lists.ts @@ -0,0 +1,47 @@ +import { UpdatePriceListWorkflowInputDTO } from "@medusajs/types" +import { + WorkflowData, + createWorkflow, + parallelize, + transform, +} from "@medusajs/workflows-sdk" +import { + updatePriceListsStep, + upsertPriceListPricesStep, + validatePriceListsStep, + validateVariantPriceLinksStep, +} from "../steps" + +type WorkflowInput = { price_lists_data: UpdatePriceListWorkflowInputDTO[] } + +export const updatePriceListsWorkflowId = "update-price-lists" +export const updatePriceListsWorkflow = createWorkflow( + updatePriceListsWorkflowId, + (input: WorkflowData): WorkflowData => { + const [priceListsMap, variantPriceMap] = parallelize( + validatePriceListsStep(input.price_lists_data), + validateVariantPriceLinksStep(input.price_lists_data) + ) + + const updatePricesInput = transform( + { priceListsMap, variantPriceMap, input }, + (data) => ({ + data: data.input.price_lists_data, + price_lists_map: data.priceListsMap, + variant_price_map: data.variantPriceMap, + }) + ) + + upsertPriceListPricesStep(updatePricesInput) + + const updatePriceListInput = transform({ input }, (data) => { + return data.input.price_lists_data.map((priceListData) => { + delete priceListData.prices + + return priceListData + }) + }) + + updatePriceListsStep(updatePriceListInput) + } +) diff --git a/packages/core-flows/src/price-list/workflows/upsert-price-list-prices.ts b/packages/core-flows/src/price-list/workflows/upsert-price-list-prices.ts new file mode 100644 index 0000000000..3fb4f409ea --- /dev/null +++ b/packages/core-flows/src/price-list/workflows/upsert-price-list-prices.ts @@ -0,0 +1,31 @@ +import { UpdatePriceListWorkflowInputDTO } from "@medusajs/types" +import { + WorkflowData, + createWorkflow, + parallelize, +} from "@medusajs/workflows-sdk" +import { + upsertPriceListPricesStep, + validatePriceListsStep, + validateVariantPriceLinksStep, +} from "../steps" + +type WorkflowInput = { + price_lists_data: Pick[] +} + +export const upsertPriceListPricesWorkflowId = "upsert-price-list-prices" +export const upsertPriceListPricesWorkflow = createWorkflow( + upsertPriceListPricesWorkflowId, + (input: WorkflowData): WorkflowData => { + const [_, variantPriceMap] = parallelize( + validatePriceListsStep(input.price_lists_data), + validateVariantPriceLinksStep(input.price_lists_data) + ) + + upsertPriceListPricesStep({ + data: input.price_lists_data, + variant_price_map: variantPriceMap, + }) + } +) diff --git a/packages/medusa/src/api-v2/admin/campaigns/validators.ts b/packages/medusa/src/api-v2/admin/campaigns/validators.ts index 415f41c5c3..a32398ffc2 100644 --- a/packages/medusa/src/api-v2/admin/campaigns/validators.ts +++ b/packages/medusa/src/api-v2/admin/campaigns/validators.ts @@ -1,4 +1,4 @@ -import { FindParams, extendedFindParamsMixin } from "../../../types/common" +import { Type } from "class-transformer" import { IsArray, IsDateString, @@ -9,10 +9,9 @@ import { IsString, ValidateNested, } from "class-validator" -import { Transform, Type } from "class-transformer" +import { FindParams, extendedFindParamsMixin } from "../../../types/common" import { CampaignBudgetType } from "@medusajs/utils" -import { transformOptionalDate } from "../../../utils/validators/date-transform" export class AdminGetCampaignsCampaignParams extends FindParams {} diff --git a/packages/medusa/src/api-v2/admin/price-lists/[id]/prices/route.ts b/packages/medusa/src/api-v2/admin/price-lists/[id]/prices/route.ts new file mode 100644 index 0000000000..c80183e59c --- /dev/null +++ b/packages/medusa/src/api-v2/admin/price-lists/[id]/prices/route.ts @@ -0,0 +1,68 @@ +import { + removePriceListPricesWorkflow, + upsertPriceListPricesWorkflow, +} from "@medusajs/core-flows" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../../../types/routing" +import { listPriceLists } from "../../queries" +import { + adminPriceListRemoteQueryFields, + defaultAdminPriceListFields, +} from "../../query-config" +import { + AdminDeletePriceListsPriceListPricesReq, + AdminPostPriceListsPriceListPricesReq, +} from "../../validators" + +export const POST = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const { prices } = req.validatedBody + const id = req.params.id + const workflow = upsertPriceListPricesWorkflow(req.scope) + const { errors } = await workflow.run({ + input: { + price_lists_data: [{ id, prices }], + }, + throwOnError: false, + }) + + if (Array.isArray(errors) && errors[0]) { + throw errors[0].error + } + + const [[priceList]] = await listPriceLists({ + container: req.scope, + remoteQueryFields: adminPriceListRemoteQueryFields, + apiFields: defaultAdminPriceListFields, + variables: { filters: { id }, skip: 0, take: 1 }, + }) + + res.status(200).json({ price_list: priceList }) +} + +export const DELETE = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const { ids } = req.validatedBody + const workflow = removePriceListPricesWorkflow(req.scope) + + const { errors } = await workflow.run({ + input: { ids }, + throwOnError: false, + }) + + if (Array.isArray(errors) && errors[0]) { + throw errors[0].error + } + + res.status(200).json({ + ids, + object: "price_list_prices", + deleted: true, + }) +} diff --git a/packages/medusa/src/api-v2/admin/price-lists/[id]/route.ts b/packages/medusa/src/api-v2/admin/price-lists/[id]/route.ts index 0392457639..31bba5eb48 100644 --- a/packages/medusa/src/api-v2/admin/price-lists/[id]/route.ts +++ b/packages/medusa/src/api-v2/admin/price-lists/[id]/route.ts @@ -1,10 +1,18 @@ +import { + deletePriceListsWorkflow, + updatePriceListsWorkflow, +} from "@medusajs/core-flows" import { MedusaError } from "@medusajs/utils" import { AuthenticatedMedusaRequest, MedusaResponse, } from "../../../../types/routing" import { listPriceLists } from "../queries" -import { adminPriceListRemoteQueryFields } from "../query-config" +import { + adminPriceListRemoteQueryFields, + defaultAdminPriceListFields, +} from "../query-config" +import { AdminPostPriceListsPriceListReq } from "../validators" export const GET = async ( req: AuthenticatedMedusaRequest, @@ -31,3 +39,52 @@ export const GET = async ( res.status(200).json({ price_list: priceList }) } + +export const POST = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const id = req.params.id + const workflow = updatePriceListsWorkflow(req.scope) + + const { errors } = await workflow.run({ + input: { price_lists_data: [{ id, ...req.validatedBody }] }, + throwOnError: false, + }) + + if (Array.isArray(errors) && errors[0]) { + throw errors[0].error + } + + const [[priceList]] = await listPriceLists({ + container: req.scope, + remoteQueryFields: adminPriceListRemoteQueryFields, + apiFields: defaultAdminPriceListFields, + variables: { filters: { id }, skip: 0, take: 1 }, + }) + + res.status(200).json({ price_list: priceList }) +} + +export const DELETE = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const id = req.params.id + const workflow = deletePriceListsWorkflow(req.scope) + + const { errors } = await workflow.run({ + input: { ids: [id] }, + throwOnError: false, + }) + + if (Array.isArray(errors) && errors[0]) { + throw errors[0].error + } + + res.status(200).json({ + id, + object: "price_list", + deleted: true, + }) +} diff --git a/packages/medusa/src/api-v2/admin/price-lists/middlewares.ts b/packages/medusa/src/api-v2/admin/price-lists/middlewares.ts index 6895af8a9b..78a2f6ae1c 100644 --- a/packages/medusa/src/api-v2/admin/price-lists/middlewares.ts +++ b/packages/medusa/src/api-v2/admin/price-lists/middlewares.ts @@ -1,10 +1,14 @@ -import { transformQuery } from "../../../api/middlewares" +import { transformBody, transformQuery } from "../../../api/middlewares" import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" import { authenticate } from "../../../utils/authenticate-middleware" import * as QueryConfig from "./query-config" import { + AdminDeletePriceListsPriceListPricesReq, AdminGetPriceListsParams, AdminGetPriceListsPriceListParams, + AdminPostPriceListsPriceListPricesReq, + AdminPostPriceListsPriceListReq, + AdminPostPriceListsReq, } from "./validators" export const adminPriceListsRoutesMiddlewares: MiddlewareRoute[] = [ @@ -33,4 +37,24 @@ export const adminPriceListsRoutesMiddlewares: MiddlewareRoute[] = [ ), ], }, + { + method: ["POST"], + matcher: "/admin/price-lists", + middlewares: [transformBody(AdminPostPriceListsReq)], + }, + { + method: ["POST"], + matcher: "/admin/price-lists/:id", + middlewares: [transformBody(AdminPostPriceListsPriceListReq)], + }, + { + method: ["POST"], + matcher: "/admin/price-lists/:id/prices", + middlewares: [transformBody(AdminPostPriceListsPriceListPricesReq)], + }, + { + method: ["DELETE"], + matcher: "/admin/price-lists/:id/prices", + middlewares: [transformBody(AdminDeletePriceListsPriceListPricesReq)], + }, ] diff --git a/packages/medusa/src/api-v2/admin/price-lists/queries/list-price-lists.ts b/packages/medusa/src/api-v2/admin/price-lists/queries/list-price-lists.ts index 6e23f486b6..8d6fe3b81d 100644 --- a/packages/medusa/src/api-v2/admin/price-lists/queries/list-price-lists.ts +++ b/packages/medusa/src/api-v2/admin/price-lists/queries/list-price-lists.ts @@ -1,11 +1,8 @@ -import { - MedusaContainer, - PriceListRuleDTO, - PriceSetMoneyAmountDTO, - ProductVariantDTO, -} from "@medusajs/types" +import { MedusaContainer } from "@medusajs/types" import { ContainerRegistrationKeys, + buildPriceListRules, + buildPriceSetPricesForCore, remoteQueryObjectFromString, } from "@medusajs/utils" import { cleanResponseData } from "../../../../utils/clean-response-data" @@ -37,7 +34,7 @@ export async function listPriceLists({ for (const priceList of priceLists) { priceList.rules = buildPriceListRules(priceList.price_list_rules || []) - priceList.prices = buildPriceSetPrices( + priceList.prices = buildPriceSetPricesForCore( priceList.price_set_money_amounts || [] ) } @@ -48,43 +45,3 @@ export async function listPriceLists({ return [sanitizedPriceLists, metadata.count] } - -function buildPriceListRules( - priceListRules: PriceListRuleDTO[] -): Record { - return priceListRules.reduce((acc, curr) => { - const ruleAttribute = curr.rule_type.rule_attribute - const ruleValues = curr.price_list_rule_values || [] - - if (ruleAttribute) { - acc[ruleAttribute] = ruleValues.map((ruleValue) => ruleValue.value) - } - - return acc - }, {}) -} - -function buildPriceSetPrices( - priceSetMoneyAmounts: (PriceSetMoneyAmountDTO & { - price_set: PriceSetMoneyAmountDTO["price_set"] & { - variant?: ProductVariantDTO - } - })[] -): Record[] { - return priceSetMoneyAmounts.map((priceSetMoneyAmount) => { - const productVariant = priceSetMoneyAmount.price_set?.variant - const rules = priceSetMoneyAmount.price_rules?.reduce((acc, curr) => { - if (curr.rule_type.rule_attribute) { - acc[curr.rule_type.rule_attribute] = curr.value - } - - return acc - }, {}) - - return { - ...priceSetMoneyAmount.money_amount, - variant_id: productVariant?.id ?? null, - rules, - } - }) -} diff --git a/packages/medusa/src/api-v2/admin/price-lists/route.ts b/packages/medusa/src/api-v2/admin/price-lists/route.ts index 49da9ebfcd..f60af32fa3 100644 --- a/packages/medusa/src/api-v2/admin/price-lists/route.ts +++ b/packages/medusa/src/api-v2/admin/price-lists/route.ts @@ -1,9 +1,14 @@ +import { createPriceListsWorkflow } from "@medusajs/core-flows" import { AuthenticatedMedusaRequest, MedusaResponse, } from "../../../types/routing" import { listPriceLists } from "./queries" -import { adminPriceListRemoteQueryFields } from "./query-config" +import { + adminPriceListRemoteQueryFields, + defaultAdminPriceListFields, +} from "./query-config" +import { AdminPostPriceListsReq } from "./validators" export const GET = async ( req: AuthenticatedMedusaRequest, @@ -29,3 +34,31 @@ export const GET = async ( limit, }) } + +export const POST = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const workflow = createPriceListsWorkflow(req.scope) + const { result, errors } = await workflow.run({ + input: { price_lists_data: [req.validatedBody] }, + throwOnError: false, + }) + + if (Array.isArray(errors) && errors[0]) { + throw errors[0].error + } + + const [[priceList]] = await listPriceLists({ + container: req.scope, + apiFields: defaultAdminPriceListFields, + remoteQueryFields: adminPriceListRemoteQueryFields, + variables: { + filters: { id: result[0].id }, + skip: 0, + take: 1, + }, + }) + + res.status(200).json({ price_list: priceList }) +} diff --git a/packages/medusa/src/api-v2/admin/price-lists/validators.ts b/packages/medusa/src/api-v2/admin/price-lists/validators.ts index 1fb1b262dd..879d4d34e8 100644 --- a/packages/medusa/src/api-v2/admin/price-lists/validators.ts +++ b/packages/medusa/src/api-v2/admin/price-lists/validators.ts @@ -1,4 +1,152 @@ +import { PriceListStatus, PriceListType } from "@medusajs/types" +import { Transform, Type } from "class-transformer" +import { + IsArray, + IsEnum, + IsInt, + IsObject, + IsOptional, + IsString, + ValidateIf, + ValidateNested, +} from "class-validator" import { FindParams } from "../../../types/common" +import { transformOptionalDate } from "../../../utils/validators/date-transform" export class AdminGetPriceListsParams extends FindParams {} export class AdminGetPriceListsPriceListParams extends FindParams {} + +export class AdminPostPriceListsReq { + @IsString() + title: string + + @IsString() + description: string + + @IsOptional() + @Transform(transformOptionalDate) + starts_at?: string + + @IsOptional() + @Transform(transformOptionalDate) + ends_at?: string + + @IsOptional() + @IsEnum(PriceListStatus) + status?: PriceListStatus + + @IsEnum(PriceListType) + type: PriceListType + + @IsArray() + @Type(() => AdminPriceListPricesCreateReq) + @ValidateNested({ each: true }) + prices: AdminPriceListPricesCreateReq[] + + @IsOptional() + @IsObject() + rules?: Record +} + +export class AdminPriceListPricesCreateReq { + @IsString() + currency_code: string + + @IsInt() + amount: number + + @IsString() + variant_id: string + + @IsOptional() + @IsInt() + min_quantity?: number + + @IsOptional() + @IsInt() + max_quantity?: number + + @IsOptional() + @IsObject() + rules?: Record +} + +export class AdminPostPriceListsPriceListReq { + @IsString() + @IsOptional() + title?: string + + @IsString() + @IsOptional() + description?: string + + @IsOptional() + @Transform(transformOptionalDate) + starts_at?: string + + @IsOptional() + @Transform(transformOptionalDate) + ends_at?: string + + @IsOptional() + @IsEnum(PriceListStatus) + status?: PriceListStatus + + @IsOptional() + @IsEnum(PriceListType) + type?: PriceListType + + @IsOptional() + @IsArray() + prices: (AdminPriceListPricesCreateReq | AdminPriceListPricesUpdateReq)[] + + @IsOptional() + @IsObject() + rules?: Record +} + +export class AdminPostPriceListsPriceListPricesReq { + @IsOptional() + @IsArray() + prices: (AdminPriceListPricesCreateReq | AdminPriceListPricesUpdateReq)[] +} + +export class AdminDeletePriceListsPriceListPricesReq { + @IsOptional() + @IsArray() + @IsString({ each: true }) + ids: string[] +} + +export class AdminPriceListPricesUpdateReq { + @IsOptional() + @IsString() + id: string + + @IsOptional() + @ValidateIf((object) => !object.id) + @IsString() + currency_code?: string + + @IsOptional() + @ValidateIf((object) => !object.id) + @IsInt() + amount?: number + + @IsOptional() + @ValidateIf((object) => !object.id) + @IsString() + variant_id: string + + @IsOptional() + @IsInt() + min_quantity?: number + + @IsOptional() + @IsInt() + max_quantity?: number + + @IsOptional() + @IsObject() + rules?: Record +} diff --git a/packages/medusa/src/api/routes/admin/price-lists/add-prices-batch.ts b/packages/medusa/src/api/routes/admin/price-lists/add-prices-batch.ts index 4d1d1cb423..fe283d40e6 100644 --- a/packages/medusa/src/api/routes/admin/price-lists/add-prices-batch.ts +++ b/packages/medusa/src/api/routes/admin/price-lists/add-prices-batch.ts @@ -1,6 +1,3 @@ -import { updatePriceLists } from "@medusajs/core-flows" -import { MedusaContainer } from "@medusajs/types" -import { MedusaV2Flag } from "@medusajs/utils" import { Type } from "class-transformer" import { IsArray, IsBoolean, IsOptional, ValidateNested } from "class-validator" import { EntityManager } from "typeorm" @@ -9,7 +6,6 @@ import { PriceList } from "../../../.." import PriceListService from "../../../../services/price-list" import { AdminPriceListPricesUpdateReq } from "../../../../types/price-list" import { validator } from "../../../../utils/validator" -import { getPriceListPricingModule } from "./modules-queries" /** * @oas [post] /admin/price-lists/{id}/prices/batch @@ -124,48 +120,22 @@ import { getPriceListPricingModule } from "./modules-queries" */ export default async (req, res) => { const { id } = req.params - let priceList - const featureFlagRouter = req.scope.resolve("featureFlagRouter") const manager: EntityManager = req.scope.resolve("manager") const priceListService: PriceListService = req.scope.resolve("priceListService") const validated = await validator(AdminPostPriceListPricesPricesReq, req.body) - if (featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)) { - const updatePriceListWorkflow = updatePriceLists(req.scope) + await manager.transaction(async (transactionManager) => { + await priceListService + .withTransaction(transactionManager) + .addPrices(id, validated.prices, validated.override) + }) - const input = { - price_lists: [ - { - id, - ...validated, - }, - ], - } - - await updatePriceListWorkflow.run({ - input, - context: { - manager, - }, - }) - - priceList = await getPriceListPricingModule(id, { - container: req.scope as MedusaContainer, - }) - } else { - await manager.transaction(async (transactionManager) => { - await priceListService - .withTransaction(transactionManager) - .addPrices(id, validated.prices, validated.override) - }) - - priceList = await priceListService.retrieve(id, { - select: defaultAdminPriceListFields as (keyof PriceList)[], - relations: defaultAdminPriceListRelations, - }) - } + const priceList = await priceListService.retrieve(id, { + select: defaultAdminPriceListFields as (keyof PriceList)[], + relations: defaultAdminPriceListRelations, + }) res.json({ price_list: priceList }) } diff --git a/packages/medusa/src/api/routes/admin/price-lists/create-price-list.ts b/packages/medusa/src/api/routes/admin/price-lists/create-price-list.ts index b2ed0a3e4c..25fec0bb6c 100644 --- a/packages/medusa/src/api/routes/admin/price-lists/create-price-list.ts +++ b/packages/medusa/src/api/routes/admin/price-lists/create-price-list.ts @@ -1,11 +1,4 @@ -import { MedusaContainer, PricingTypes, WorkflowTypes } from "@medusajs/types" -import { - FlagRouter, - MedusaV2Flag, - PriceListStatus, - PriceListType, -} from "@medusajs/utils" -import { createPriceLists } from "@medusajs/core-flows" +import { PriceListStatus, PriceListType } from "@medusajs/utils" import { Transform, Type } from "class-transformer" import { IsArray, @@ -26,7 +19,6 @@ import { CreatePriceListInput, } from "../../../../types/price-list" import { FeatureFlagDecorators } from "../../../../utils/feature-flag-decorators" -import { getPriceListPricingModule } from "./modules-queries" import { transformOptionalDate } from "../../../../utils/validators/date-transform" /** @@ -155,57 +147,17 @@ export default async (req: Request, res) => { req.scope.resolve("priceListService") const manager: EntityManager = req.scope.resolve("manager") - const featureFlagRouter: FlagRouter = req.scope.resolve("featureFlagRouter") - let priceList - const isMedusaV2FlagEnabled = featureFlagRouter.isFeatureEnabled( - MedusaV2Flag.key - ) + let priceList = await manager.transaction(async (transactionManager) => { + return await priceListService + .withTransaction(transactionManager) + .create(req.validatedBody as CreatePriceListInput) + }) - if (isMedusaV2FlagEnabled) { - const createPriceListWorkflow = createPriceLists(req.scope) - const validatedInput = req.validatedBody as CreatePriceListInput - const rules: PricingTypes.CreatePriceListRules = {} - const customerGroups = validatedInput?.customer_groups || [] - delete validatedInput.customer_groups - - if (customerGroups.length) { - rules["customer_group_id"] = customerGroups.map((cg) => cg.id) - } - - const input = { - price_lists: [ - { - ...validatedInput, - rules, - }, - ], - } as WorkflowTypes.PriceListWorkflow.CreatePriceListWorkflowInputDTO - - const { result } = await createPriceListWorkflow.run({ - input, - context: { - manager, - }, - }) - - priceList = result[0]!.priceList - - priceList = await getPriceListPricingModule(priceList.id, { - container: req.scope as MedusaContainer, - }) - } else { - priceList = await manager.transaction(async (transactionManager) => { - return await priceListService - .withTransaction(transactionManager) - .create(req.validatedBody as CreatePriceListInput) - }) - - priceList = await priceListService.retrieve(priceList.id, { - select: defaultAdminPriceListFields as (keyof PriceList)[], - relations: defaultAdminPriceListRelations, - }) - } + priceList = await priceListService.retrieve(priceList.id, { + select: defaultAdminPriceListFields as (keyof PriceList)[], + relations: defaultAdminPriceListRelations, + }) res.json({ price_list: priceList }) } diff --git a/packages/medusa/src/api/routes/admin/price-lists/delete-price-list.ts b/packages/medusa/src/api/routes/admin/price-lists/delete-price-list.ts index 49493e0608..1455460886 100644 --- a/packages/medusa/src/api/routes/admin/price-lists/delete-price-list.ts +++ b/packages/medusa/src/api/routes/admin/price-lists/delete-price-list.ts @@ -1,6 +1,3 @@ -import { WorkflowTypes } from "@medusajs/types" -import { FlagRouter, MedusaV2Flag } from "@medusajs/utils" -import { removePriceLists } from "@medusajs/core-flows" import { EntityManager } from "typeorm" import PriceListService from "../../../../services/price-list" @@ -86,34 +83,13 @@ import PriceListService from "../../../../services/price-list" */ export default async (req, res) => { const { id } = req.params - - const featureFlagRouter: FlagRouter = req.scope.resolve("featureFlagRouter") const manager: EntityManager = req.scope.resolve("manager") - const isMedusaV2FlagEnabled = featureFlagRouter.isFeatureEnabled( - MedusaV2Flag.key - ) - - if (isMedusaV2FlagEnabled) { - const removePriceListsWorkflow = removePriceLists(req.scope) - - const input = { - price_lists: [id], - } as WorkflowTypes.PriceListWorkflow.RemovePriceListWorkflowInputDTO - - await removePriceListsWorkflow.run({ - input, - context: { - manager, - }, - }) - } else { - const priceListService: PriceListService = - req.scope.resolve("priceListService") - await manager.transaction(async (transactionManager) => { - await priceListService.withTransaction(transactionManager).delete(id) - }) - } + const priceListService: PriceListService = + req.scope.resolve("priceListService") + await manager.transaction(async (transactionManager) => { + await priceListService.withTransaction(transactionManager).delete(id) + }) res.json({ id, diff --git a/packages/medusa/src/api/routes/admin/price-lists/delete-prices-batch.ts b/packages/medusa/src/api/routes/admin/price-lists/delete-prices-batch.ts index da07615e5f..bcb9ea3a02 100644 --- a/packages/medusa/src/api/routes/admin/price-lists/delete-prices-batch.ts +++ b/packages/medusa/src/api/routes/admin/price-lists/delete-prices-batch.ts @@ -1,10 +1,7 @@ -import { FlagRouter, MedusaV2Flag } from "@medusajs/utils" import { ArrayNotEmpty, IsString } from "class-validator" import { EntityManager } from "typeorm" import PriceListService from "../../../../services/price-list" import { validator } from "../../../../utils/validator" -import { WorkflowTypes } from "@medusajs/types" -import { removePriceListPrices } from "@medusajs/core-flows" /** * @oas [delete] /admin/price-lists/{id}/prices/batch @@ -101,43 +98,20 @@ import { removePriceListPrices } from "@medusajs/core-flows" */ export default async (req, res) => { const { id } = req.params - const validated = await validator( AdminDeletePriceListPricesPricesReq, req.body ) + const manager: EntityManager = req.scope.resolve("manager") const priceListService: PriceListService = req.scope.resolve("priceListService") - const featureFlagRouter: FlagRouter = req.scope.resolve("featureFlagRouter") - const manager: EntityManager = req.scope.resolve("manager") - - const isMedusaV2FlagEnabled = featureFlagRouter.isFeatureEnabled( - MedusaV2Flag.key - ) - - if (isMedusaV2FlagEnabled) { - const deletePriceListPricesWorkflow = removePriceListPrices(req.scope) - - const input = { - price_list_id: id, - money_amount_ids: validated.price_ids, - } as WorkflowTypes.PriceListWorkflow.RemovePriceListPricesWorkflowInputDTO - - await deletePriceListPricesWorkflow.run({ - input, - context: { - manager, - }, - }) - } else { - await manager.transaction(async (transactionManager) => { - await priceListService - .withTransaction(transactionManager) - .deletePrices(id, validated.price_ids) - }) - } + await manager.transaction(async (transactionManager) => { + await priceListService + .withTransaction(transactionManager) + .deletePrices(id, validated.price_ids) + }) res.json({ ids: validated.price_ids, object: "money-amount", deleted: true }) } diff --git a/packages/medusa/src/api/routes/admin/price-lists/delete-product-prices.ts b/packages/medusa/src/api/routes/admin/price-lists/delete-product-prices.ts index 878c43234e..fa90b05a7c 100644 --- a/packages/medusa/src/api/routes/admin/price-lists/delete-product-prices.ts +++ b/packages/medusa/src/api/routes/admin/price-lists/delete-product-prices.ts @@ -1,6 +1,3 @@ -import { WorkflowTypes } from "@medusajs/types" -import { FlagRouter, MedusaV2Flag } from "@medusajs/utils" -import { removePriceListProductPrices } from "@medusajs/core-flows" import { EntityManager } from "typeorm" import PriceListService from "../../../../services/price-list" @@ -94,47 +91,18 @@ import PriceListService from "../../../../services/price-list" */ export default async (req, res) => { const { id, product_id } = req.params - const priceListService: PriceListService = req.scope.resolve("priceListService") - const featureFlagRouter: FlagRouter = req.scope.resolve("featureFlagRouter") const manager: EntityManager = req.scope.resolve("manager") - - const isMedusaV2FlagEnabled = featureFlagRouter.isFeatureEnabled( - MedusaV2Flag.key - ) - let deletedPriceIds: string[] = [] - if (isMedusaV2FlagEnabled) { - const deletePriceListProductsWorkflow = removePriceListProductPrices( - req.scope - ) - - const input = { - product_ids: [product_id], - price_list_id: id, - } as WorkflowTypes.PriceListWorkflow.RemovePriceListProductsWorkflowInputDTO - - const { result } = await deletePriceListProductsWorkflow.run({ - input, - context: { - manager, - }, - }) - - deletedPriceIds = result - } else { - const [deletedIds] = await manager.transaction( - async (transactionManager) => { - return await priceListService - .withTransaction(transactionManager) - .deleteProductPrices(id, [product_id]) - } - ) - deletedPriceIds = deletedIds - } + const [deletedIds] = await manager.transaction(async (transactionManager) => { + return await priceListService + .withTransaction(transactionManager) + .deleteProductPrices(id, [product_id]) + }) + deletedPriceIds = deletedIds return res.json({ ids: deletedPriceIds, diff --git a/packages/medusa/src/api/routes/admin/price-lists/delete-products-prices-batch.ts b/packages/medusa/src/api/routes/admin/price-lists/delete-products-prices-batch.ts index a4d482ecd5..4732855ca3 100644 --- a/packages/medusa/src/api/routes/admin/price-lists/delete-products-prices-batch.ts +++ b/packages/medusa/src/api/routes/admin/price-lists/delete-products-prices-batch.ts @@ -1,11 +1,8 @@ -import { FlagRouter, MedusaV2Flag } from "@medusajs/utils" -import { removePriceListProductPrices } from "@medusajs/core-flows" import { ArrayNotEmpty, IsString } from "class-validator" import { Request, Response } from "express" import { EntityManager } from "typeorm" import PriceListService from "../../../../services/price-list" import { validator } from "../../../../utils/validator" -import { WorkflowTypes } from "@medusajs/types" /** * @oas [delete] /admin/price-lists/{id}/products/prices/batch @@ -114,43 +111,16 @@ export default async (req: Request, res: Response) => { const priceListService: PriceListService = req.scope.resolve("priceListService") - const featureFlagRouter: FlagRouter = req.scope.resolve("featureFlagRouter") const manager: EntityManager = req.scope.resolve("manager") - - const isMedusaV2FlagEnabled = featureFlagRouter.isFeatureEnabled( - MedusaV2Flag.key - ) - let deletedPriceIds: string[] = [] - if (isMedusaV2FlagEnabled) { - const deletePriceListProductsWorkflow = removePriceListProductPrices( - req.scope - ) + const [deletedIds] = await manager.transaction(async (transactionManager) => { + return await priceListService + .withTransaction(transactionManager) + .deleteProductPrices(id, validated.product_ids) + }) - const input = { - product_ids: validated.product_ids, - price_list_id: id, - } as WorkflowTypes.PriceListWorkflow.RemovePriceListProductsWorkflowInputDTO - - const { result } = await deletePriceListProductsWorkflow.run({ - input, - context: { - manager, - }, - }) - deletedPriceIds = result - } else { - const [deletedIds] = await manager.transaction( - async (transactionManager) => { - return await priceListService - .withTransaction(transactionManager) - .deleteProductPrices(id, validated.product_ids) - } - ) - - deletedPriceIds = deletedIds - } + deletedPriceIds = deletedIds return res.json({ ids: deletedPriceIds, diff --git a/packages/medusa/src/api/routes/admin/price-lists/delete-variant-prices.ts b/packages/medusa/src/api/routes/admin/price-lists/delete-variant-prices.ts index a7a7155120..4e3b3be5da 100644 --- a/packages/medusa/src/api/routes/admin/price-lists/delete-variant-prices.ts +++ b/packages/medusa/src/api/routes/admin/price-lists/delete-variant-prices.ts @@ -1,8 +1,5 @@ -import { FlagRouter, MedusaV2Flag } from "@medusajs/utils" -import { removePriceListVariantPrices } from "@medusajs/core-flows" import { EntityManager } from "typeorm" import PriceListService from "../../../../services/price-list" -import { WorkflowTypes } from "@medusajs/types" /** * @oas [delete] /admin/price-lists/{id}/variants/{variant_id}/prices @@ -97,46 +94,16 @@ export default async (req, res) => { const priceListService: PriceListService = req.scope.resolve("priceListService") - const featureFlagRouter: FlagRouter = req.scope.resolve("featureFlagRouter") const manager: EntityManager = req.scope.resolve("manager") - const isMedusaV2FlagEnabled = featureFlagRouter.isFeatureEnabled( - MedusaV2Flag.key - ) - - let deletedPriceIds: string[] = [] - - if (isMedusaV2FlagEnabled) { - const deletePriceListProductsWorkflow = removePriceListVariantPrices( - req.scope - ) - - const input = { - variant_ids: [variant_id], - price_list_id: id, - } as WorkflowTypes.PriceListWorkflow.RemovePriceListVariantsWorkflowInputDTO - - const { result } = await deletePriceListProductsWorkflow.run({ - input, - context: { - manager, - }, - }) - deletedPriceIds = result - } else { - const [deletedIds] = await manager.transaction( - async (transactionManager) => { - return await priceListService - .withTransaction(transactionManager) - .deleteVariantPrices(id, [variant_id]) - } - ) - - deletedPriceIds = deletedIds - } + const [deletedIds] = await manager.transaction(async (transactionManager) => { + return await priceListService + .withTransaction(transactionManager) + .deleteVariantPrices(id, [variant_id]) + }) return res.json({ - ids: deletedPriceIds, + ids: deletedIds, object: "money-amount", deleted: true, }) diff --git a/packages/medusa/src/api/routes/admin/price-lists/list-price-list-products.ts b/packages/medusa/src/api/routes/admin/price-lists/list-price-list-products.ts index 46f5321453..97c68928a7 100644 --- a/packages/medusa/src/api/routes/admin/price-lists/list-price-list-products.ts +++ b/packages/medusa/src/api/routes/admin/price-lists/list-price-list-products.ts @@ -11,13 +11,12 @@ import { extendedFindParamsMixin, } from "../../../../types/common" -import { FlagRouter, MedusaV2Flag } from "@medusajs/utils" import { Type } from "class-transformer" import { Request } from "express" import { ProductStatus } from "../../../../models" import PriceListService from "../../../../services/price-list" import { FilterableProductProps } from "../../../../types/product" -import { IsType, listProducts } from "../../../../utils" +import { IsType } from "../../../../utils" /** * @oas [get] /admin/price-lists/{id}/products @@ -228,10 +227,6 @@ import { IsType, listProducts } from "../../../../utils" export default async (req: Request, res) => { const { id } = req.params const { offset, limit } = req.validatedQuery - const featureFlagRouter: FlagRouter = req.scope.resolve("featureFlagRouter") - let products - let count - const priceListService: PriceListService = req.scope.resolve("priceListService") @@ -240,19 +235,11 @@ export default async (req: Request, res) => { price_list_id: [id], } - if (featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)) { - ;[products, count] = await listProducts( - req.scope, - filterableFields, - req.listConfig - ) - } else { - ;[products, count] = await priceListService.listProducts( - id, - filterableFields, - req.listConfig - ) - } + const [products, count] = await priceListService.listProducts( + id, + filterableFields, + req.listConfig + ) res.json({ products, diff --git a/packages/medusa/src/api/routes/admin/price-lists/update-price-list.ts b/packages/medusa/src/api/routes/admin/price-lists/update-price-list.ts index a6cb94a7f1..ad783a433e 100644 --- a/packages/medusa/src/api/routes/admin/price-lists/update-price-list.ts +++ b/packages/medusa/src/api/routes/admin/price-lists/update-price-list.ts @@ -1,5 +1,4 @@ -import { MedusaContainer, PricingTypes, WorkflowTypes } from "@medusajs/types" -import { MedusaV2Flag, PriceListStatus, PriceListType } from "@medusajs/utils" +import { PriceListStatus, PriceListType } from "@medusajs/utils" import { IsArray, IsBoolean, @@ -10,7 +9,6 @@ import { } from "class-validator" import { defaultAdminPriceListFields, defaultAdminPriceListRelations } from "." -import { updatePriceLists } from "@medusajs/core-flows" import { Transform, Type } from "class-transformer" import { EntityManager } from "typeorm" import { PriceList } from "../../../.." @@ -19,7 +17,6 @@ import PriceListService from "../../../../services/price-list" import { AdminPriceListPricesUpdateReq } from "../../../../types/price-list" import { FeatureFlagDecorators } from "../../../../utils/feature-flag-decorators" import { validator } from "../../../../utils/validator" -import { getPriceListPricingModule } from "./modules-queries" import { transformOptionalDate } from "../../../../utils/validators/date-transform" /** @@ -119,8 +116,6 @@ import { transformOptionalDate } from "../../../../utils/validators/date-transfo */ export default async (req, res) => { const { id } = req.params - let priceList - const featureFlagRouter = req.scope.resolve("featureFlagRouter") const manager: EntityManager = req.scope.resolve("manager") const priceListService: PriceListService = req.scope.resolve("priceListService") @@ -130,48 +125,16 @@ export default async (req, res) => { req.body ) - if (featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)) { - const updateVariantsWorkflow = updatePriceLists(req.scope) - const customerGroups = validated.customer_groups - delete validated.customer_groups + await manager.transaction(async (transactionManager) => { + return await priceListService + .withTransaction(transactionManager) + .update(id, validated) + }) - const updatePriceListInput = { - id, - ...validated, - } as PricingTypes.UpdatePriceListDTO - - if (Array.isArray(customerGroups)) { - updatePriceListInput.rules = { - customer_group_id: customerGroups.map((group) => group.id), - } - } - - const input = { - price_lists: [updatePriceListInput], - } as WorkflowTypes.PriceListWorkflow.UpdatePriceListWorkflowInputDTO - - await updateVariantsWorkflow.run({ - input, - context: { - manager, - }, - }) - - priceList = await getPriceListPricingModule(id, { - container: req.scope as MedusaContainer, - }) - } else { - await manager.transaction(async (transactionManager) => { - return await priceListService - .withTransaction(transactionManager) - .update(id, validated) - }) - - priceList = await priceListService.retrieve(id, { - select: defaultAdminPriceListFields as (keyof PriceList)[], - relations: defaultAdminPriceListRelations, - }) - } + const priceList = await priceListService.retrieve(id, { + select: defaultAdminPriceListFields as (keyof PriceList)[], + relations: defaultAdminPriceListRelations, + }) res.json({ price_list: priceList }) } diff --git a/packages/medusa/src/scripts/migrate-to-pricing-module.ts b/packages/medusa/src/scripts/migrate-to-pricing-module.ts index 796130dd5b..20a82e55e1 100644 --- a/packages/medusa/src/scripts/migrate-to-pricing-module.ts +++ b/packages/medusa/src/scripts/migrate-to-pricing-module.ts @@ -6,7 +6,7 @@ import express from "express" import loaders from "../loaders" import Logger from "../loaders/logger" import { PriceList } from "../models" -import { CurrencyService, PriceListService } from "../services" +import { PriceListService } from "../services" import { createDefaultRuleTypes } from "./utils/create-default-rule-types" import { migrateProductVariantPricing } from "./utils/migrate-money-amounts-to-pricing-module" @@ -109,7 +109,7 @@ const migratePriceLists = async (container: AwilixContainer) => { pricingModuleService.addPriceListPrices( priceListsToUpdate.map((priceList) => { return { - priceListId: priceList.id, + price_list_id: priceList.id, prices: priceList.prices .filter((price) => variantIdPriceSetIdMap.has(price.variants?.[0]?.id) @@ -136,7 +136,7 @@ const migratePriceLists = async (container: AwilixContainer) => { pricingModuleService.createPriceLists( priceListsToCreate.map( ({ name: title, prices, customer_groups, ...priceList }) => { - const createData: PricingTypes.CreatePriceListDTO = { + const createData: any = { ...priceList, title, } diff --git a/packages/pricing/integration-tests/__tests__/services/pricing-module/calculate-price.spec.ts b/packages/pricing/integration-tests/__tests__/services/pricing-module/calculate-price.spec.ts index a18fe8bfb6..61fbd0209f 100644 --- a/packages/pricing/integration-tests/__tests__/services/pricing-module/calculate-price.spec.ts +++ b/packages/pricing/integration-tests/__tests__/services/pricing-module/calculate-price.spec.ts @@ -1,3 +1,4 @@ +import { Modules } from "@medusajs/modules-sdk" import { CreatePriceRuleDTO, CreatePriceSetDTO, @@ -5,9 +6,8 @@ import { PricingTypes, } from "@medusajs/types" import { PriceListType } from "@medusajs/utils" +import { SuiteOptions, moduleIntegrationTestRunner } from "medusa-test-utils" import { seedPriceData } from "../../../__fixtures__/seed-price-data" -import { Modules } from "@medusajs/modules-sdk" -import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils" jest.setTimeout(30000) @@ -16,7 +16,7 @@ const defaultRules = { region_id: ["DE", "DK"], } -const defaultPriceListPrices: PricingTypes.PriceListPriceDTO[] = [ +const defaultPriceListPrices: PricingTypes.CreatePriceListPriceDTO[] = [ { amount: 232, currency_code: "PLN", diff --git a/packages/pricing/integration-tests/__tests__/services/pricing-module/price-list-rule.spec.ts b/packages/pricing/integration-tests/__tests__/services/pricing-module/price-list-rule.spec.ts index 610cb5fd21..8a114939d4 100644 --- a/packages/pricing/integration-tests/__tests__/services/pricing-module/price-list-rule.spec.ts +++ b/packages/pricing/integration-tests/__tests__/services/pricing-module/price-list-rule.spec.ts @@ -1,10 +1,10 @@ +import { Modules } from "@medusajs/modules-sdk" import { IPricingModuleService } from "@medusajs/types" import { SqlEntityManager } from "@mikro-orm/postgresql" +import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils" import { createPriceLists } from "../../../__fixtures__/price-list" import { createPriceListRules } from "../../../__fixtures__/price-list-rules" import { createRuleTypes } from "../../../__fixtures__/rule-type" -import { Modules } from "@medusajs/modules-sdk" -import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils" jest.setTimeout(30000) @@ -240,7 +240,7 @@ moduleIntegrationTestRunner({ ]) await service.setPriceListRules({ - priceListId: "price-list-1", + price_list_id: "price-list-1", rules: { sales_channel: "sc-1", }, @@ -280,7 +280,7 @@ moduleIntegrationTestRunner({ ]) await service.setPriceListRules({ - priceListId: "price-list-1", + price_list_id: "price-list-1", rules: { sales_channel: ["sc-1", "sc-2"], }, @@ -315,7 +315,7 @@ moduleIntegrationTestRunner({ describe("removePriceListRules", () => { it("should remove a priceListRule from a priceList", async () => { await service.removePriceListRules({ - priceListId: "price-list-1", + price_list_id: "price-list-1", rules: ["currency_code"], }) diff --git a/packages/pricing/integration-tests/__tests__/services/pricing-module/price-list.spec.ts b/packages/pricing/integration-tests/__tests__/services/pricing-module/price-list.spec.ts index b569c3e5f3..ce2d755514 100644 --- a/packages/pricing/integration-tests/__tests__/services/pricing-module/price-list.spec.ts +++ b/packages/pricing/integration-tests/__tests__/services/pricing-module/price-list.spec.ts @@ -1,8 +1,8 @@ import { Modules } from "@medusajs/modules-sdk" import { IPricingModuleService } from "@medusajs/types" +import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils" import { createPriceLists } from "../../../__fixtures__/price-list" import { createPriceSets } from "../../../__fixtures__/price-set" -import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils" jest.setTimeout(30000) @@ -666,7 +666,7 @@ moduleIntegrationTestRunner({ it("should add a price to a priceList successfully", async () => { await service.addPriceListPrices([ { - priceListId: "price-list-1", + price_list_id: "price-list-1", prices: [ { amount: 123, @@ -734,7 +734,7 @@ moduleIntegrationTestRunner({ try { await service.addPriceListPrices([ { - priceListId: "price-list-1", + price_list_id: "price-list-1", prices: [ { amount: 123, @@ -774,7 +774,7 @@ moduleIntegrationTestRunner({ await service.addPriceListPrices([ { - priceListId: "price-list-1", + price_list_id: "price-list-1", prices: [ { amount: 123, @@ -843,6 +843,203 @@ moduleIntegrationTestRunner({ ) }) }) + + describe("updatePriceListPrices", () => { + it("should update a price to a priceList successfully", async () => { + const [priceSet] = await service.create([ + { + rules: [ + { rule_attribute: "region_id" }, + { rule_attribute: "customer_group_id" }, + ], + }, + ]) + + await service.addPriceListPrices([ + { + price_list_id: "price-list-1", + prices: [ + { + id: "test-price-id", + amount: 123, + currency_code: "EUR", + price_set_id: priceSet.id, + rules: { + region_id: "test", + }, + } as any, + ], + }, + ]) + + await service.updatePriceListPrices([ + { + price_list_id: "price-list-1", + prices: [ + { + id: "test-price-id", + price_set_id: priceSet.id, + rules: { + region_id: "new test", + customer_group_id: "new test", + }, + }, + ], + }, + ]) + + const [priceList] = await service.listPriceLists( + { id: ["price-list-1"] }, + { + relations: [ + "price_set_money_amounts.money_amount", + "price_set_money_amounts.price_set", + "price_set_money_amounts.price_rules", + "price_set_money_amounts.price_rules.rule_type", + "price_list_rules.price_list_rule_values", + "price_list_rules.rule_type", + ], + select: [ + "id", + "price_set_money_amounts.price_rules.value", + "price_set_money_amounts.price_rules.rule_type.rule_attribute", + "price_set_money_amounts.rules_count", + "price_set_money_amounts.money_amount.amount", + "price_set_money_amounts.money_amount.currency_code", + "price_set_money_amounts.money_amount.price_list_id", + "price_list_rules.price_list_rule_values.value", + "price_list_rules.rule_type.rule_attribute", + ], + } + ) + + expect(priceList).toEqual( + expect.objectContaining({ + id: expect.any(String), + price_set_money_amounts: expect.arrayContaining([ + expect.objectContaining({ + rules_count: 2, + price_rules: expect.arrayContaining([ + expect.objectContaining({ + value: "new test", + rule_type: expect.objectContaining({ + rule_attribute: "region_id", + }), + }), + expect.objectContaining({ + value: "new test", + rule_type: expect.objectContaining({ + rule_attribute: "customer_group_id", + }), + }), + ]), + price_list: expect.objectContaining({ + id: expect.any(String), + }), + money_amount: expect.objectContaining({ + amount: 123, + currency_code: "EUR", + }), + }), + ]), + price_list_rules: [], + }) + ) + }) + + it("should fail to add a price with non-existing rule-types in the price-set to a priceList", async () => { + await service.createRuleTypes([ + { name: "twitter_handle", rule_attribute: "twitter_handle" }, + { name: "region_id", rule_attribute: "region_id" }, + ]) + + const [priceSet] = await service.create([ + { rules: [{ rule_attribute: "region_id" }] }, + ]) + + await service.addPriceListPrices([ + { + price_list_id: "price-list-1", + prices: [ + { + id: "test-price-id", + amount: 123, + currency_code: "EUR", + price_set_id: priceSet.id, + rules: { region_id: "test" }, + } as any, + ], + }, + ]) + + const error = await service + .updatePriceListPrices([ + { + price_list_id: "price-list-1", + prices: [ + { + id: "test-price-id", + amount: 123, + price_set_id: priceSet.id, + rules: { twitter_handle: "owjuhl" }, + }, + ], + }, + ]) + .catch((e) => e) + + expect(error.message).toEqual( + `Invalid rule type configuration: Price set rules doesn't exist for rule_attribute "twitter_handle" in price set ${priceSet.id}` + ) + }) + }) + + describe("removePrices", () => { + it("should remove prices from a priceList successfully", async () => { + const [priceSet] = await service.create([ + { rules: [{ rule_attribute: "region_id" }] }, + ]) + + await service.addPriceListPrices([ + { + price_list_id: "price-list-1", + prices: [ + { + amount: 123, + currency_code: "EUR", + price_set_id: priceSet.id, + }, + ], + }, + ]) + + let [priceList] = await service.listPriceLists( + { id: ["price-list-1"] }, + { + relations: ["price_set_money_amounts"], + select: ["price_set_money_amounts.id"], + } + ) + + await service.removePrices( + priceList.price_set_money_amounts!.map((psma) => psma.id) + ) + ;[priceList] = await service.listPriceLists( + { id: ["price-list-1"] }, + { + relations: ["price_set_money_amounts"], + select: ["id", "price_set_money_amounts.id"], + } + ) + + expect(priceList).toEqual( + expect.objectContaining({ + id: expect.any(String), + price_set_money_amounts: [], + }) + ) + }) + }) }) }, }) diff --git a/packages/pricing/integration-tests/__tests__/services/pricing-module/price-set.spec.ts b/packages/pricing/integration-tests/__tests__/services/pricing-module/price-set.spec.ts index 0902499da0..236d9b67fb 100644 --- a/packages/pricing/integration-tests/__tests__/services/pricing-module/price-set.spec.ts +++ b/packages/pricing/integration-tests/__tests__/services/pricing-module/price-set.spec.ts @@ -5,10 +5,10 @@ import { } from "@medusajs/types" import { SqlEntityManager } from "@mikro-orm/postgresql" -import { PriceSetRuleType } from "../../../../src" -import { seedPriceData } from "../../../__fixtures__/seed-price-data" import { Modules } from "@medusajs/modules-sdk" import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils" +import { PriceSetRuleType } from "../../../../src" +import { seedPriceData } from "../../../__fixtures__/seed-price-data" jest.setTimeout(30000) diff --git a/packages/pricing/src/services/pricing-module.ts b/packages/pricing/src/services/pricing-module.ts index a3b15029b3..f55d92f3ba 100644 --- a/packages/pricing/src/services/pricing-module.ts +++ b/packages/pricing/src/services/pricing-module.ts @@ -1031,9 +1031,7 @@ export default class PricingModuleService< .flat() const existingPriceListRules = await this.listPriceListRules( - { - id: priceListRuleIds, - }, + { id: priceListRuleIds }, {}, sharedContext ) @@ -1046,9 +1044,7 @@ export default class PricingModuleService< } const ruleTypes = await this.listRuleTypes( - { - rule_attribute: ruleAttributes, - }, + { rule_attribute: ruleAttributes }, { take: null }, sharedContext ) @@ -1059,6 +1055,7 @@ export default class PricingModuleService< for (const priceListData of data) { const { rules, ...priceListOnlyData } = priceListData + const updatePriceListData: any = { ...priceListOnlyData, } @@ -1083,12 +1080,7 @@ export default class PricingModuleService< if (!ruleType) { ;[ruleType] = await this.createRuleTypes( - [ - { - name: ruleAttribute, - rule_attribute: ruleAttribute, - }, - ], + [{ name: ruleAttribute, rule_attribute: ruleAttribute }], sharedContext ) @@ -1107,12 +1099,7 @@ export default class PricingModuleService< for (const ruleValue of ruleValues as string[]) { await this.priceListRuleValueService_.create( - [ - { - price_list_rule: priceListRule, - value: ruleValue, - }, - ], + [{ price_list_rule: priceListRule, value: ruleValue }], sharedContext ) } @@ -1161,6 +1148,185 @@ export default class PricingModuleService< }) } + @InjectManager("baseRepository_") + async updatePriceListPrices( + data: PricingTypes.UpdatePriceListPricesDTO[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + return await this.updatePriceListPrices_(data, sharedContext) + } + + @InjectTransactionManager("baseRepository_") + protected async updatePriceListPrices_( + data: PricingTypes.UpdatePriceListPricesDTO[], + sharedContext: Context = {} + ): Promise { + const ruleTypeAttributes: string[] = [] + const priceListIds: string[] = [] + const moneyAmountIds: string[] = [] + const priceSetIds = data + .map((d) => d.prices.map((price) => price.price_set_id)) + .flat() + + for (const priceListData of data) { + priceListIds.push(priceListData.price_list_id) + + for (const price of priceListData.prices) { + moneyAmountIds.push(price.id) + ruleTypeAttributes.push(...Object.keys(price.rules || {})) + } + } + + const moneyAmounts = await this.listMoneyAmounts( + { id: moneyAmountIds }, + { + take: null, + relations: [ + "price_set_money_amount", + "price_set_money_amount.price_rules", + ], + }, + sharedContext + ) + + const moneyAmountMap: Map = new Map( + moneyAmounts.map((ma) => [ma.id, ma]) + ) + + const ruleTypes = await this.listRuleTypes( + { rule_attribute: ruleTypeAttributes }, + { take: null }, + sharedContext + ) + + const ruleTypeMap: Map = new Map( + ruleTypes.map((rt) => [rt.rule_attribute, rt]) + ) + + const priceSets = await this.list( + { id: priceSetIds }, + { relations: ["rule_types"] }, + sharedContext + ) + + const priceSetRuleTypeMap: Map> = priceSets.reduce( + (acc, curr) => { + const priceSetRuleAttributeSet: Set = + acc.get(curr.id) || new Set() + + for (const rt of curr.rule_types ?? []) { + priceSetRuleAttributeSet.add(rt.rule_attribute) + } + + acc.set(curr.id, priceSetRuleAttributeSet) + return acc + }, + new Map() + ) + + const ruleTypeErrors: string[] = [] + + for (const priceListData of data) { + for (const price of priceListData.prices) { + for (const ruleAttribute of Object.keys(price.rules ?? {})) { + if ( + !priceSetRuleTypeMap.get(price.price_set_id)?.has(ruleAttribute) + ) { + ruleTypeErrors.push( + `rule_attribute "${ruleAttribute}" in price set ${price.price_set_id}` + ) + } + } + } + } + + if (ruleTypeErrors.length) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `Invalid rule type configuration: Price set rules doesn't exist for ${ruleTypeErrors.join( + ", " + )}` + ) + } + + const priceLists = await this.listPriceLists( + { id: priceListIds }, + { take: null }, + sharedContext + ) + + const priceListMap = new Map(priceLists.map((p) => [p.id, p])) + + for (const { price_list_id: priceListId, prices } of data) { + const priceList = priceListMap.get(priceListId) + + if (!priceList) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `Price list with id: ${priceListId} not found` + ) + } + + const moneyAmountsToUpdate: PricingTypes.UpdateMoneyAmountDTO[] = [] + const priceRulesToDelete: string[] = [] + const priceRulesToCreate: PricingTypes.CreatePriceRuleDTO[] = [] + const psmaToUpdate: ServiceTypes.UpdatePriceSetMoneyAmountDTO[] = [] + + for (const price of prices) { + const { rules, price_set_id, ...priceData } = price + const moneyAmount = moneyAmountMap.get(price.id)! + const priceSetMoneyAmount = moneyAmount.price_set_money_amount! + const priceRules = priceSetMoneyAmount.price_rules! + + moneyAmountsToUpdate.push(priceData) + + if (typeof rules === "undefined") { + continue + } + + psmaToUpdate.push({ + id: priceSetMoneyAmount!.id, + rules_count: Object.keys(rules).length, + }) + + priceRulesToDelete.push(...priceRules.map((pr) => pr.id)) + priceRulesToCreate.push( + ...Object.entries(rules).map(([ruleAttribute, ruleValue]) => ({ + price_set_id: price.price_set_id, + rule_type_id: ruleTypeMap.get(ruleAttribute)!.id, + value: ruleValue, + price_set_money_amount_id: priceSetMoneyAmount.id, + })) + ) + } + + await Promise.all([ + this.moneyAmountService_.update(moneyAmountsToUpdate), + this.priceRuleService_.delete(priceRulesToDelete), + this.priceRuleService_.create(priceRulesToCreate), + this.priceSetMoneyAmountService_.update(psmaToUpdate), + ]) + } + + return priceLists + } + + @InjectManager("baseRepository_") + async removePrices( + ids: string[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + await this.removePrices_(ids, sharedContext) + } + + @InjectTransactionManager("baseRepository_") + protected async removePrices_( + ids: string[], + sharedContext: Context = {} + ): Promise { + await this.priceSetMoneyAmountService_.delete(ids, sharedContext) + } + @InjectManager("baseRepository_") async addPriceListPrices( data: PricingTypes.AddPriceListPricesDTO[], @@ -1179,7 +1345,7 @@ export default class PricingModuleService< const priceSetIds: string[] = [] for (const priceListData of data) { - priceListIds.push(priceListData.priceListId) + priceListIds.push(priceListData.price_list_id) for (const price of priceListData.prices) { ruleTypeAttributes.push(...Object.keys(price.rules || {})) @@ -1251,7 +1417,7 @@ export default class PricingModuleService< const priceListMap = new Map(priceLists.map((p) => [p.id, p])) - for (const { priceListId, prices } of data) { + for (const { price_list_id: priceListId, prices } of data) { const priceList = priceListMap.get(priceListId) if (!priceList) { @@ -1322,7 +1488,7 @@ export default class PricingModuleService< sharedContext: Context = {} ): Promise { const priceLists = await this.priceListService_.list( - { id: data.map((d) => d.priceListId) }, + { id: data.map((d) => d.price_list_id) }, { relations: ["price_list_rules", "price_list_rules.rule_type"], }, @@ -1347,7 +1513,7 @@ export default class PricingModuleService< const priceRuleValues = new Map>() - for (const { priceListId, rules } of data) { + for (const { price_list_id: priceListId, rules } of data) { const priceList = priceListMap.get(priceListId) if (!priceList) { @@ -1465,7 +1631,7 @@ export default class PricingModuleService< sharedContext: Context = {} ): Promise { const priceLists = await this.priceListService_.list( - { id: data.map((d) => d.priceListId) }, + { id: data.map((d) => d.price_list_id) }, { relations: ["price_list_rules", "price_list_rules.rule_type"], }, @@ -1475,7 +1641,7 @@ export default class PricingModuleService< const priceListMap = new Map(priceLists.map((p) => [p.id, p])) const idsToDelete: string[] = [] - for (const { priceListId, rules } of data) { + for (const { price_list_id: priceListId, rules } of data) { const priceList = priceListMap.get(priceListId) if (!priceList) { diff --git a/packages/pricing/src/types/repositories/price-list.ts b/packages/pricing/src/types/repositories/price-list.ts index 1d5931b2d0..5f1762a7d2 100644 --- a/packages/pricing/src/types/repositories/price-list.ts +++ b/packages/pricing/src/types/repositories/price-list.ts @@ -3,8 +3,8 @@ import { PriceListStatus, PriceListType } from "@medusajs/utils" export interface CreatePriceListDTO { title: string description: string - starts_at?: Date | string | null - ends_at?: Date | string | null + starts_at?: string | null + ends_at?: string | null status?: PriceListStatus type?: PriceListType rules_count?: number @@ -13,8 +13,8 @@ export interface CreatePriceListDTO { export interface UpdatePriceListDTO { id: string title?: string - starts_at?: Date | string | null - ends_at?: Date | string | null + starts_at?: string | null + ends_at?: string | null status?: PriceListStatus number_rules?: number } diff --git a/packages/pricing/src/types/services/money-amount.ts b/packages/pricing/src/types/services/money-amount.ts index d4ba334550..2e0c6ca5e1 100644 --- a/packages/pricing/src/types/services/money-amount.ts +++ b/packages/pricing/src/types/services/money-amount.ts @@ -18,10 +18,10 @@ export interface UpdateMoneyAmountDTO { export interface MoneyAmountDTO { id: string - currency_code?: string - amount?: number - min_quantity?: number - max_quantity?: number + currency_code?: string | null + amount?: number | null + min_quantity?: number | null + max_quantity?: number | null price_set_money_amount?: PriceSetMoneyAmountDTO created_at: Date updated_at: Date diff --git a/packages/pricing/src/types/services/price-list.ts b/packages/pricing/src/types/services/price-list.ts index 60ed0d970a..4a39abdb1c 100644 --- a/packages/pricing/src/types/services/price-list.ts +++ b/packages/pricing/src/types/services/price-list.ts @@ -1,4 +1,3 @@ -import { PriceListStatus, PriceListType } from "@medusajs/utils" import { BaseFilterable, MoneyAmountDTO, @@ -6,12 +5,13 @@ import { PriceSetMoneyAmountDTO, RuleTypeDTO, } from "@medusajs/types" +import { PriceListStatus, PriceListType } from "@medusajs/utils" export interface CreatePriceListDTO { title: string description: string - starts_at?: Date | string | null - ends_at?: Date | string | null + starts_at?: string | null + ends_at?: string | null status?: PriceListStatus type?: PriceListType number_rules?: number @@ -20,8 +20,8 @@ export interface CreatePriceListDTO { export interface UpdatePriceListDTO { id: string title?: string - starts_at?: Date | string | null - ends_at?: Date | string | null + starts_at?: string | null + ends_at?: string | null status?: PriceListStatus number_rules?: number } diff --git a/packages/pricing/src/types/services/price-set-money-amount.ts b/packages/pricing/src/types/services/price-set-money-amount.ts index 11fcee3e59..6e44461ae8 100644 --- a/packages/pricing/src/types/services/price-set-money-amount.ts +++ b/packages/pricing/src/types/services/price-set-money-amount.ts @@ -11,6 +11,7 @@ export interface UpdatePriceSetMoneyAmountDTO { title?: string price_set?: PriceSetDTO money_amount?: MoneyAmountDTO + rules_count?: number } export interface CreatePriceSetMoneyAmountDTO { diff --git a/packages/types/src/pricing/common/money-amount.ts b/packages/types/src/pricing/common/money-amount.ts index 0387dd7306..05edc0429c 100644 --- a/packages/types/src/pricing/common/money-amount.ts +++ b/packages/types/src/pricing/common/money-amount.ts @@ -86,7 +86,7 @@ export interface UpdateMoneyAmountDTO { /** * The code of the currency to associate with the money amount. */ - currency_code?: string + currency_code?: string | null /** * The price of this money amount. */ diff --git a/packages/types/src/pricing/common/price-list.ts b/packages/types/src/pricing/common/price-list.ts index a28ba2a796..708417e052 100644 --- a/packages/types/src/pricing/common/price-list.ts +++ b/packages/types/src/pricing/common/price-list.ts @@ -1,8 +1,11 @@ -import { CreateMoneyAmountDTO, MoneyAmountDTO } from "./money-amount"; - -import { BaseFilterable } from "../../dal"; -import { PriceSetMoneyAmountDTO } from "./price-set-money-amount"; -import { RuleTypeDTO } from "./rule-type"; +import { BaseFilterable } from "../../dal" +import { + CreateMoneyAmountDTO, + MoneyAmountDTO, + UpdateMoneyAmountDTO, +} from "./money-amount" +import { PriceSetMoneyAmountDTO } from "./price-set-money-amount" +import { RuleTypeDTO } from "./rule-type" /** * @enum @@ -106,7 +109,18 @@ export interface PriceListDTO { * * The prices associated with a price list. */ -export interface PriceListPriceDTO extends CreateMoneyAmountDTO { +export interface CreatePriceListPriceDTO extends CreateMoneyAmountDTO { + /** + * The ID of the associated price set. + */ + price_set_id: string + /** + * The rules to add to the price. The object's keys are rule types' `rule_attribute` attribute, and values are the value of that rule associated with this price. + */ + rules?: CreatePriceSetPriceRules +} + +export interface UpdatePriceListPriceDTO extends UpdateMoneyAmountDTO { /** * The ID of the associated price set. */ @@ -152,11 +166,11 @@ export interface CreatePriceListDTO { /** * The price list is enabled starting from this date. */ - starts_at?: Date | string | null + starts_at?: string | null /** * The price list expires after this date. */ - ends_at?: Date | string | null + ends_at?: string | null /** * The price list's status. */ @@ -176,7 +190,7 @@ export interface CreatePriceListDTO { /** * The prices associated with the price list. */ - prices?: PriceListPriceDTO[] + prices?: CreatePriceListPriceDTO[] } /** @@ -200,11 +214,11 @@ export interface UpdatePriceListDTO { /** * The price list is enabled starting from this date. */ - starts_at?: Date | string | null + starts_at?: string | null /** * The price list expires after this date. */ - ends_at?: Date | string | null + ends_at?: string | null /** * The price list's status. */ @@ -412,11 +426,27 @@ export interface AddPriceListPricesDTO { /** * The ID of the price list to add prices to. */ - priceListId: string + price_list_id: string /** * The prices to add. */ - prices: PriceListPriceDTO[] + prices: CreatePriceListPriceDTO[] +} + +/** + * @interface + * + * The prices to be added to a price list. + */ +export interface UpdatePriceListPricesDTO { + /** + * The ID of the price list to add prices to. + */ + price_list_id: string + /** + * The prices to add. + */ + prices: UpdatePriceListPriceDTO[] } /** @@ -428,7 +458,7 @@ export interface SetPriceListRulesDTO { /** * The ID of the price list to add rules to. */ - priceListId: string + price_list_id: string /** * The rules to add to the price list. Each key of the object is a rule type's `rule_attribute`, and its value * is the value(s) of the rule. @@ -445,7 +475,7 @@ export interface RemovePriceListRulesDTO { /** * The ID of the price list to remove rules from. */ - priceListId: string + price_list_id: string /** * The rules to remove from the price list. Each item being a rule type's `rule_attribute`. */ diff --git a/packages/types/src/pricing/common/price-rule.ts b/packages/types/src/pricing/common/price-rule.ts index 52556f0025..e652a5c482 100644 --- a/packages/types/src/pricing/common/price-rule.ts +++ b/packages/types/src/pricing/common/price-rule.ts @@ -1,6 +1,5 @@ import { BaseFilterable } from "../../dal" import { PriceSetDTO } from "./price-set" -import { PriceSetMoneyAmountDTO } from "./price-set-money-amount" import { RuleTypeDTO } from "./rule-type" /** @@ -73,18 +72,10 @@ export interface CreatePriceRuleDTO { * The ID of the associated price set. */ price_set_id?: string - /** - * The ID or object of the associated price set. - */ - price_set?: string | PriceSetDTO /** * The ID of the associated rule type. */ rule_type_id?: string - /** - * The ID of the associated rule type. - */ - rule_type?: string | RuleTypeDTO /** * The value of the price rule. */ @@ -97,10 +88,6 @@ export interface CreatePriceRuleDTO { * The ID of the associated price set money amount. */ price_set_money_amount_id?: string - /** - * The ID or object of the associated price set money amount. - */ - price_set_money_amount?: string | PriceSetMoneyAmountDTO } /** diff --git a/packages/types/src/pricing/common/price-set-money-amount.ts b/packages/types/src/pricing/common/price-set-money-amount.ts index 80ae6490cd..8ceededac5 100644 --- a/packages/types/src/pricing/common/price-set-money-amount.ts +++ b/packages/types/src/pricing/common/price-set-money-amount.ts @@ -1,8 +1,8 @@ -import { BaseFilterable } from "../../dal"; -import { MoneyAmountDTO } from "./money-amount"; -import { PriceListDTO } from "./price-list"; -import { PriceRuleDTO } from "./price-rule"; -import { PriceSetDTO } from "./price-set"; +import { BaseFilterable } from "../../dal" +import { MoneyAmountDTO } from "./money-amount" +import { PriceListDTO } from "./price-list" +import { PriceRuleDTO } from "./price-rule" +import { PriceSetDTO } from "./price-set" /** * @interface diff --git a/packages/types/src/pricing/index.ts b/packages/types/src/pricing/index.ts index eade309433..f27a0d44a5 100644 --- a/packages/types/src/pricing/index.ts +++ b/packages/types/src/pricing/index.ts @@ -1,2 +1,3 @@ export * from "./common" export * from "./service" +export * from "./workflows" diff --git a/packages/types/src/pricing/service.ts b/packages/types/src/pricing/service.ts index dba09f9bd0..c502c34f5d 100644 --- a/packages/types/src/pricing/service.ts +++ b/packages/types/src/pricing/service.ts @@ -33,6 +33,7 @@ import { SetPriceListRulesDTO, UpdateMoneyAmountDTO, UpdatePriceListDTO, + UpdatePriceListPricesDTO, UpdatePriceListRuleDTO, UpdatePriceRuleDTO, UpdatePriceSetDTO, @@ -3397,6 +3398,11 @@ export interface IPricingModuleService extends IModuleService { sharedContext?: Context ): Promise + updatePriceListPrices( + data: UpdatePriceListPricesDTO[], + sharedContext?: Context + ): Promise + /** * This method is used to set the rules of a price list. * @@ -3454,4 +3460,6 @@ export interface IPricingModuleService extends IModuleService { data: RemovePriceListRulesDTO, sharedContext?: Context ): Promise + + removePrices(ids: string[], sharedContext?: Context): Promise } diff --git a/packages/types/src/pricing/workflows.ts b/packages/types/src/pricing/workflows.ts new file mode 100644 index 0000000000..d6ca3d42b1 --- /dev/null +++ b/packages/types/src/pricing/workflows.ts @@ -0,0 +1,41 @@ +import { PriceListStatus } from "./common" + +export interface CreatePriceListPriceWorkflowDTO { + amount: number + currency_code: string + variant_id: string + max_quantity?: number + min_quantity?: number + rules?: Record +} + +export interface UpdatePriceListPriceWorkflowDTO { + id: string + amount?: number + currency_code?: string + variant_id?: string + max_quantity?: number + min_quantity?: number + rules?: Record +} + +export interface CreatePriceListWorkflowInputDTO { + title: string + description: string + starts_at?: string | null + ends_at?: string | null + status?: PriceListStatus + rules?: Record + prices?: CreatePriceListPriceWorkflowDTO[] +} + +export interface UpdatePriceListWorkflowInputDTO { + id: string + title?: string + description?: string + starts_at?: string | null + ends_at?: string | null + status?: PriceListStatus + rules?: Record + prices?: (UpdatePriceListPriceWorkflowDTO | CreatePriceListPriceWorkflowDTO)[] +} diff --git a/packages/utils/src/common/get-selects-and-relations-from-object-array.ts b/packages/utils/src/common/get-selects-and-relations-from-object-array.ts index 3352ece7df..1a491c2015 100644 --- a/packages/utils/src/common/get-selects-and-relations-from-object-array.ts +++ b/packages/utils/src/common/get-selects-and-relations-from-object-array.ts @@ -3,6 +3,9 @@ import { isObject } from "./is-object" export function getSelectsAndRelationsFromObjectArray( dataArray: object[], + options: { objectFields: string[] } = { + objectFields: [], + }, prefix?: string ): { selects: string[] @@ -13,10 +16,11 @@ export function getSelectsAndRelationsFromObjectArray( for (const data of dataArray) { for (const [key, value] of Object.entries(data)) { - if (isObject(value)) { + if (isObject(value) && !options.objectFields.includes(key)) { relations.push(setKey(key, prefix)) const res = getSelectsAndRelationsFromObjectArray( [value], + options, setKey(key, prefix) ) selects.push(...res.selects) @@ -25,6 +29,7 @@ export function getSelectsAndRelationsFromObjectArray( relations.push(setKey(key, prefix)) const res = getSelectsAndRelationsFromObjectArray( value, + options, setKey(key, prefix) ) selects.push(...res.selects) diff --git a/packages/utils/src/pricing/builders.ts b/packages/utils/src/pricing/builders.ts new file mode 100644 index 0000000000..520fa3a424 --- /dev/null +++ b/packages/utils/src/pricing/builders.ts @@ -0,0 +1,70 @@ +import { + PriceListRuleDTO, + PriceRuleDTO, + PriceSetMoneyAmountDTO, + ProductVariantDTO, + UpdatePriceListPriceDTO, +} from "@medusajs/types" + +export function buildPriceListRules( + priceListRules: PriceListRuleDTO[] +): Record { + return priceListRules.reduce((acc, curr) => { + const ruleAttribute = curr.rule_type.rule_attribute + const ruleValues = curr.price_list_rule_values || [] + + acc[ruleAttribute] = ruleValues.map((ruleValue) => ruleValue.value) + + return acc + }, {}) +} + +export function buildPriceSetRules( + priceRules: PriceRuleDTO[] +): Record { + return priceRules.reduce((acc, curr) => { + const ruleAttribute = curr.rule_type.rule_attribute + const ruleValue = curr.value + + acc[ruleAttribute] = ruleValue + + return acc + }, {}) +} + +export function buildPriceSetPricesForCore( + priceSetMoneyAmounts: (PriceSetMoneyAmountDTO & { + price_set: PriceSetMoneyAmountDTO["price_set"] & { + variant?: ProductVariantDTO + } + })[] +): Record[] { + return priceSetMoneyAmounts.map((priceSetMoneyAmount) => { + const productVariant = (priceSetMoneyAmount.price_set as any).variant + const rules: Record = priceSetMoneyAmount.price_rules + ? buildPriceSetRules(priceSetMoneyAmount.price_rules) + : {} + + return { + ...priceSetMoneyAmount.money_amount, + variant_id: productVariant?.id ?? null, + rules, + } + }) +} + +export function buildPriceSetPricesForModule( + priceSetMoneyAmounts: PriceSetMoneyAmountDTO[] +): UpdatePriceListPriceDTO[] { + return priceSetMoneyAmounts.map((priceSetMoneyAmount) => { + const rules: Record = priceSetMoneyAmount.price_rules + ? buildPriceSetRules(priceSetMoneyAmount.price_rules) + : {} + + return { + ...priceSetMoneyAmount.money_amount!, + price_set_id: priceSetMoneyAmount.price_set!?.id!, + rules, + } + }) +} diff --git a/packages/utils/src/pricing/index.ts b/packages/utils/src/pricing/index.ts index f62072ad0d..02a4467e94 100644 --- a/packages/utils/src/pricing/index.ts +++ b/packages/utils/src/pricing/index.ts @@ -1,2 +1,3 @@ +export * from "./builders" export * from "./price-list" export * from "./rule-type" diff --git a/packages/workflows-sdk/src/utils/composer/create-step.ts b/packages/workflows-sdk/src/utils/composer/create-step.ts index 54e438135f..8d8cc6c423 100644 --- a/packages/workflows-sdk/src/utils/composer/create-step.ts +++ b/packages/workflows-sdk/src/utils/composer/create-step.ts @@ -2,9 +2,9 @@ import { TransactionStepsDefinition, WorkflowManager, } from "@medusajs/orchestration" -import { isString, OrchestrationUtils } from "@medusajs/utils" +import { OrchestrationUtils, isString } from "@medusajs/utils" import { ulid } from "ulid" -import { resolveValue, StepResponse } from "./helpers" +import { StepResponse, resolveValue } from "./helpers" import { proxify } from "./helpers/proxy" import { CreateWorkflowComposerContext, diff --git a/packages/workflows-sdk/src/utils/composer/helpers/step-response.ts b/packages/workflows-sdk/src/utils/composer/helpers/step-response.ts index d7a95d0263..e98c3e4948 100644 --- a/packages/workflows-sdk/src/utils/composer/helpers/step-response.ts +++ b/packages/workflows-sdk/src/utils/composer/helpers/step-response.ts @@ -1,5 +1,5 @@ -import { OrchestrationUtils } from "@medusajs/utils" import { PermanentStepFailureError } from "@medusajs/orchestration" +import { OrchestrationUtils } from "@medusajs/utils" /** * This class is used to create the response returned by a step. A step return its data by returning an instance of `StepResponse`. @@ -40,7 +40,7 @@ export class StepResponse { * Creates a StepResponse that indicates that the step has failed and the retry mechanism should not kick in anymore. * * @param message - An optional message to be logged. - * + * * @example * import { Product } from "@medusajs/medusa" * import { @@ -48,11 +48,11 @@ export class StepResponse { * StepResponse, * createWorkflow * } from "@medusajs/workflows-sdk" - * + * * interface CreateProductInput { * title: string * } - * + * * export const createProductStep = createStep( * "createProductStep", * async function ( @@ -62,7 +62,7 @@ export class StepResponse { * const productService = context.container.resolve( * "productService" * ) - * + * * try { * const product = await productService.create(input) * return new StepResponse({ @@ -75,22 +75,22 @@ export class StepResponse { * } * } * ) - * + * * interface WorkflowInput { * title: string * } - * + * * const myWorkflow = createWorkflow< * WorkflowInput, * Product * >("my-workflow", (input) => { * // Everything here will be executed and resolved later * // during the execution. Including the data access. - * + * * const product = createProductStep(input) * } * ) - * + * * myWorkflow() * .run({ * input: {