diff --git a/integration-tests/modules/__tests__/fixtures/fulfillment/index.ts b/integration-tests/modules/__tests__/fixtures/fulfillment/index.ts new file mode 100644 index 0000000000..0dc846217e --- /dev/null +++ b/integration-tests/modules/__tests__/fixtures/fulfillment/index.ts @@ -0,0 +1,138 @@ +import { + CreateFulfillmentDTO, + CreateShippingOptionDTO, + IFulfillmentModuleService, +} from "@medusajs/types" + +export function generateCreateFulfillmentData( + data: Partial & { + provider_id: string + shipping_option_id: string + } +) { + const randomString = Math.random().toString(36).substring(7) + + return { + location_id: "test-location", + packed_at: null, + shipped_at: null, + delivered_at: null, + canceled_at: null, + data: null, + provider_id: data.provider_id, + shipping_option_id: data.shipping_option_id, + metadata: data.metadata ?? null, + delivery_address: data.delivery_address ?? { + address_1: "test-address_" + randomString, + address_2: "test-address_" + randomString, + city: "test-city_" + randomString, + postal_code: "test-postal-code_" + randomString, + country_code: "test-country-code_" + randomString, + province: "test-province_" + randomString, + phone: "test-phone_" + randomString, + full_name: "test-full-name_" + randomString, + }, + items: data.items ?? [ + { + title: "test-title_" + randomString, + sku: "test-sku_" + randomString, + quantity: 1, + barcode: "test-barcode_" + randomString, + }, + ], + labels: data.labels ?? [ + { + tracking_number: "test-tracking-number_" + randomString, + tracking_url: "test-tracking-url_" + randomString, + label_url: "test-label-url_" + randomString, + }, + ], + order: data.order ?? {}, + } +} + +export function generateCreateShippingOptionsData({ + name, + service_zone_id, + shipping_profile_id, + provider_id, + price_type, + rules, + type, + data, +}: Omit & { + price_type?: CreateShippingOptionDTO["price_type"] + name?: string + type?: CreateShippingOptionDTO["type"] +}): Required { + const randomString = Math.random().toString(36).substring(7) + + return { + service_zone_id: service_zone_id, + shipping_profile_id: shipping_profile_id, + provider_id: provider_id, + type: type ?? { + code: "test-type_" + randomString, + description: "test-description_" + randomString, + label: "test-label_" + randomString, + }, + data: data ?? { + amount: 1000, + }, + name: name ?? Math.random().toString(36).substring(7), + price_type: price_type ?? "flat", + rules: rules ?? [ + { + attribute: "weight", + operator: "eq", + value: "test", + }, + ], + } +} + +export async function setupFullDataFulfillmentStructure( + service: IFulfillmentModuleService, + { + providerId, + }: { + providerId: string + } +) { + const randomString = Math.random().toString(36).substring(7) + + const shippingProfile = await service.createShippingProfiles({ + // generate random string + name: "test_" + randomString, + type: "default", + }) + const fulfillmentSet = await service.create({ + name: "test_" + randomString, + type: "test-type", + }) + const serviceZone = await service.createServiceZones({ + name: "test_" + randomString, + fulfillment_set_id: fulfillmentSet.id, + geo_zones: [ + { + type: "country", + country_code: "US_" + randomString, + }, + ], + }) + + const shippingOption = await service.createShippingOptions( + generateCreateShippingOptionsData({ + provider_id: providerId, + service_zone_id: serviceZone.id, + shipping_profile_id: shippingProfile.id, + }) + ) + + await service.createFulfillment( + generateCreateFulfillmentData({ + provider_id: providerId, + shipping_option_id: shippingOption.id, + }) + ) +} diff --git a/integration-tests/modules/__tests__/fixtures/index.ts b/integration-tests/modules/__tests__/fixtures/index.ts index 1793574b72..6faab7ebde 100644 --- a/integration-tests/modules/__tests__/fixtures/index.ts +++ b/integration-tests/modules/__tests__/fixtures/index.ts @@ -1 +1,2 @@ export * from "./tax" +export * from "./fulfillment" diff --git a/integration-tests/modules/__tests__/fulfillment/index.spec.ts b/integration-tests/modules/__tests__/fulfillment/index.spec.ts new file mode 100644 index 0000000000..f4be4dc929 --- /dev/null +++ b/integration-tests/modules/__tests__/fulfillment/index.spec.ts @@ -0,0 +1,69 @@ +import { medusaIntegrationTestRunner } from "medusa-test-utils/dist" +import { setupFullDataFulfillmentStructure } from "../fixtures" +import { IFulfillmentModuleService } from "@medusajs/types" +import { ModuleRegistrationName } from "@medusajs/modules-sdk" + +jest.setTimeout(100000) + +const env = { MEDUSA_FF_MEDUSA_V2: true } + +medusaIntegrationTestRunner({ + env, + testSuite: ({ getContainer }) => { + let service: IFulfillmentModuleService + + beforeAll(() => { + const container = getContainer() + service = container.resolve(ModuleRegistrationName.FULFILLMENT) + }) + + /** + * The test runner run both the medusa migrations as well as the modules + * migrations. In order to ensure the backward compatibility + * of the migration works, we will create a full data structure. + */ + describe("Fulfillment module migrations backward compatibility", () => { + it("should allow to create a full data structure after the backward compatible migration have run on top of the medusa v1 database", async () => { + await setupFullDataFulfillmentStructure(service, { + providerId: `manual_test-provider`, + }) + + const fulfillmentSets = await service.list( + {}, + { + relations: [ + "service_zones.geo_zones", + "service_zones.shipping_options.shipping_profile", + "service_zones.shipping_options.provider", + "service_zones.shipping_options.type", + "service_zones.shipping_options.rules", + "service_zones.shipping_options.fulfillments.labels", + "service_zones.shipping_options.fulfillments.items", + "service_zones.shipping_options.fulfillments.delivery_address", + ], + } + ) + + expect(fulfillmentSets).toHaveLength(1) + + let fulfillmentSet = fulfillmentSets[0] + expect(fulfillmentSet.service_zones).toHaveLength(1) + + let serviceZone = fulfillmentSet.service_zones[0] + expect(serviceZone.geo_zones).toHaveLength(1) + expect(serviceZone.shipping_options).toHaveLength(1) + + let geoZone = serviceZone.geo_zones[0] + + let shippingOption = serviceZone.shipping_options[0] + expect(!!shippingOption.shipping_profile.deleted_at).toEqual(false) + expect(shippingOption.fulfillments).toHaveLength(1) + expect(shippingOption.rules).toHaveLength(1) + + let fulfillment = shippingOption.fulfillments[0] + expect(fulfillment.labels).toHaveLength(1) + expect(fulfillment.items).toHaveLength(1) + }) + }) + }, +}) diff --git a/integration-tests/modules/medusa-config.js b/integration-tests/modules/medusa-config.js index 73341ed119..96060a12a9 100644 --- a/integration-tests/modules/medusa-config.js +++ b/integration-tests/modules/medusa-config.js @@ -1,4 +1,5 @@ const { Modules } = require("@medusajs/modules-sdk") +const { FulfillmentModuleOptions } = require("@medusajs/fulfillment") const DB_HOST = process.env.DB_HOST const DB_USERNAME = process.env.DB_USERNAME const DB_PASSWORD = process.env.DB_PASSWORD @@ -70,5 +71,20 @@ module.exports = { [Modules.TAX]: true, [Modules.CURRENCY]: true, [Modules.PAYMENT]: true, + [Modules.FULFILLMENT]: { + /** @type {import('@medusajs/fulfillment').FulfillmentModuleOptions} */ + options: { + providers: [ + { + resolve: "@medusajs/fulfillment-manual", + options: { + config: { + "test-provider": {}, + }, + }, + }, + ], + }, + }, }, } diff --git a/packages/fulfillment/integration-tests/__fixtures__/index.ts b/packages/fulfillment/integration-tests/__fixtures__/index.ts index 26751493ac..51a4aea42f 100644 --- a/packages/fulfillment/integration-tests/__fixtures__/index.ts +++ b/packages/fulfillment/integration-tests/__fixtures__/index.ts @@ -37,7 +37,7 @@ export async function createFullDataStructure( const shippingOption = await service.createShippingOptions( generateCreateShippingOptionsData({ - fulfillment_provider_id: providerId, + provider_id: providerId, service_zone_id: serviceZone.id, shipping_profile_id: shippingProfile.id, }) diff --git a/packages/fulfillment/integration-tests/__fixtures__/shipping-options.ts b/packages/fulfillment/integration-tests/__fixtures__/shipping-options.ts index 45044638a8..8f49962e03 100644 --- a/packages/fulfillment/integration-tests/__fixtures__/shipping-options.ts +++ b/packages/fulfillment/integration-tests/__fixtures__/shipping-options.ts @@ -4,7 +4,7 @@ export function generateCreateShippingOptionsData({ name, service_zone_id, shipping_profile_id, - fulfillment_provider_id, + provider_id, price_type, rules, type, @@ -19,7 +19,7 @@ export function generateCreateShippingOptionsData({ return { service_zone_id: service_zone_id, shipping_profile_id: shipping_profile_id, - fulfillment_provider_id: fulfillment_provider_id, + provider_id: provider_id, type: type ?? { code: "test-type_" + randomString, description: "test-description_" + randomString, diff --git a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service/fulfillment.spec.ts b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service/fulfillment.spec.ts index 6129d5a82b..f161d9b975 100644 --- a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service/fulfillment.spec.ts +++ b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service/fulfillment.spec.ts @@ -49,7 +49,7 @@ moduleIntegrationTestRunner({ const shippingOption = await service.createShippingOptions( generateCreateShippingOptionsData({ - fulfillment_provider_id: providerId, + provider_id: providerId, service_zone_id: serviceZone.id, shipping_profile_id: shippingProfile.id, }) @@ -97,7 +97,7 @@ moduleIntegrationTestRunner({ const shippingOption = await service.createShippingOptions( generateCreateShippingOptionsData({ - fulfillment_provider_id: providerId, + provider_id: providerId, service_zone_id: serviceZone.id, shipping_profile_id: shippingProfile.id, }) @@ -158,7 +158,7 @@ moduleIntegrationTestRunner({ const shippingOption = await service.createShippingOptions( generateCreateShippingOptionsData({ - fulfillment_provider_id: providerId, + provider_id: providerId, service_zone_id: serviceZone.id, shipping_profile_id: shippingProfile.id, }) diff --git a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service/index.spec.ts b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service/index.spec.ts index 1a6f4bed48..5e5ec72e3e 100644 --- a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service/index.spec.ts +++ b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service/index.spec.ts @@ -38,7 +38,7 @@ async function list( relations: [ "service_zones.geo_zones", "service_zones.shipping_options.shipping_profile", - "service_zones.shipping_options.fulfillment_provider", + "service_zones.shipping_options.provider", "service_zones.shipping_options.type", "service_zones.shipping_options.rules", "service_zones.shipping_options.fulfillments.labels", diff --git a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service/shipping-option.spec.ts b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service/shipping-option.spec.ts index e4a730266e..1eae51938e 100644 --- a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service/shipping-option.spec.ts +++ b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service/shipping-option.spec.ts @@ -61,7 +61,7 @@ moduleIntegrationTestRunner({ generateCreateShippingOptionsData({ service_zone_id: fulfillmentSet.service_zones[0].id, shipping_profile_id: shippingProfile.id, - fulfillment_provider_id: providerId, + provider_id: providerId, rules: [ { attribute: "test-attribute", @@ -73,7 +73,7 @@ moduleIntegrationTestRunner({ generateCreateShippingOptionsData({ service_zone_id: fulfillmentSet.service_zones[0].id, shipping_profile_id: shippingProfile.id, - fulfillment_provider_id: providerId, + provider_id: providerId, rules: [ { attribute: "test-attribute", @@ -118,7 +118,7 @@ moduleIntegrationTestRunner({ generateCreateShippingOptionsData({ service_zone_id: fulfillmentSet.service_zones[0].id, shipping_profile_id: shippingProfile.id, - fulfillment_provider_id: providerId, + provider_id: providerId, rules: [ { attribute: "test-attribute", @@ -130,7 +130,7 @@ moduleIntegrationTestRunner({ generateCreateShippingOptionsData({ service_zone_id: fulfillmentSet.service_zones[0].id, shipping_profile_id: shippingProfile.id, - fulfillment_provider_id: providerId, + provider_id: providerId, rules: [ { attribute: "test-attribute", @@ -142,7 +142,7 @@ moduleIntegrationTestRunner({ generateCreateShippingOptionsData({ service_zone_id: fulfillmentSet.service_zones[0].id, shipping_profile_id: shippingProfile.id, - fulfillment_provider_id: providerId, + provider_id: providerId, rules: [ { attribute: "test-attribute", @@ -221,7 +221,7 @@ moduleIntegrationTestRunner({ generateCreateShippingOptionsData({ service_zone_id: serviceZone.id, shipping_profile_id: shippingProfile.id, - fulfillment_provider_id: providerId, + provider_id: providerId, }) const createdShippingOption = await service.createShippingOptions( @@ -235,7 +235,7 @@ moduleIntegrationTestRunner({ price_type: createData.price_type, service_zone_id: createData.service_zone_id, shipping_profile_id: createData.shipping_profile_id, - fulfillment_provider_id: createData.fulfillment_provider_id, + provider_id: createData.provider_id, shipping_option_type_id: expect.any(String), type: expect.objectContaining({ id: expect.any(String), @@ -274,12 +274,12 @@ moduleIntegrationTestRunner({ generateCreateShippingOptionsData({ service_zone_id: serviceZone.id, shipping_profile_id: shippingProfile.id, - fulfillment_provider_id: providerId, + provider_id: providerId, }), generateCreateShippingOptionsData({ service_zone_id: serviceZone.id, shipping_profile_id: shippingProfile.id, - fulfillment_provider_id: providerId, + provider_id: providerId, }), ] @@ -298,7 +298,7 @@ moduleIntegrationTestRunner({ price_type: data_.price_type, service_zone_id: data_.service_zone_id, shipping_profile_id: data_.shipping_profile_id, - fulfillment_provider_id: data_.fulfillment_provider_id, + provider_id: data_.provider_id, shipping_option_type_id: expect.any(String), type: expect.objectContaining({ id: expect.any(String), @@ -339,7 +339,7 @@ moduleIntegrationTestRunner({ generateCreateShippingOptionsData({ service_zone_id: serviceZone.id, shipping_profile_id: shippingProfile.id, - fulfillment_provider_id: providerId, + provider_id: providerId, rules: [ { attribute: "test-attribute", @@ -378,7 +378,7 @@ moduleIntegrationTestRunner({ const shippingOptionData = generateCreateShippingOptionsData({ service_zone_id: serviceZone.id, shipping_profile_id: shippingProfile.id, - fulfillment_provider_id: providerId, + provider_id: providerId, }) const shippingOption = await service.createShippingOptions( @@ -391,7 +391,7 @@ moduleIntegrationTestRunner({ price_type: "calculated", service_zone_id: serviceZone.id, shipping_profile_id: shippingProfile.id, - fulfillment_provider_id: providerId, + provider_id: providerId, type: { code: "updated-test", description: "updated-test", @@ -420,7 +420,7 @@ moduleIntegrationTestRunner({ price_type: updateData.price_type, service_zone_id: updateData.service_zone_id, shipping_profile_id: updateData.shipping_profile_id, - fulfillment_provider_id: updateData.fulfillment_provider_id, + provider_id: updateData.provider_id, shipping_option_type_id: expect.any(String), type: expect.objectContaining({ id: expect.any(String), @@ -476,7 +476,7 @@ moduleIntegrationTestRunner({ const shippingOptionData = generateCreateShippingOptionsData({ service_zone_id: serviceZone.id, shipping_profile_id: shippingProfile.id, - fulfillment_provider_id: providerId, + provider_id: providerId, }) const shippingOption = await service.createShippingOptions( @@ -489,7 +489,7 @@ moduleIntegrationTestRunner({ price_type: "calculated", service_zone_id: serviceZone.id, shipping_profile_id: shippingProfile.id, - fulfillment_provider_id: providerId, + provider_id: providerId, data: { amount: 2000, }, @@ -511,7 +511,7 @@ moduleIntegrationTestRunner({ price_type: updateData.price_type, service_zone_id: updateData.service_zone_id, shipping_profile_id: updateData.shipping_profile_id, - fulfillment_provider_id: updateData.fulfillment_provider_id, + provider_id: updateData.provider_id, shipping_option_type_id: expect.any(String), type: expect.objectContaining({ id: expect.any(String), @@ -568,12 +568,12 @@ moduleIntegrationTestRunner({ generateCreateShippingOptionsData({ service_zone_id: serviceZone.id, shipping_profile_id: shippingProfile.id, - fulfillment_provider_id: providerId, + provider_id: providerId, }), generateCreateShippingOptionsData({ service_zone_id: serviceZone.id, shipping_profile_id: shippingProfile.id, - fulfillment_provider_id: providerId, + provider_id: providerId, }), ] @@ -588,7 +588,7 @@ moduleIntegrationTestRunner({ price_type: "calculated", service_zone_id: serviceZone.id, shipping_profile_id: shippingProfile.id, - fulfillment_provider_id: providerId, + provider_id: providerId, type: { code: "updated-test", description: "updated-test", @@ -611,7 +611,7 @@ moduleIntegrationTestRunner({ price_type: "calculated", service_zone_id: serviceZone.id, shipping_profile_id: shippingProfile.id, - fulfillment_provider_id: providerId, + provider_id: providerId, type: { code: "updated-test", description: "updated-test", @@ -645,7 +645,7 @@ moduleIntegrationTestRunner({ price_type: data_.price_type, service_zone_id: data_.service_zone_id, shipping_profile_id: data_.shipping_profile_id, - fulfillment_provider_id: data_.fulfillment_provider_id, + provider_id: data_.provider_id, shipping_option_type_id: expect.any(String), type: expect.objectContaining({ id: expect.any(String), @@ -711,18 +711,13 @@ moduleIntegrationTestRunner({ type: "default", }) - const [fulfillmentProvider] = - await MikroOrmWrapper.forkManager().execute( - "insert into fulfillment_provider (id) values ('sp_jdafwfleiwuonl') returning id" - ) - const shippingOptionData = { id: "sp_jdafwfleiwuonl", name: "test", price_type: "flat", service_zone_id: serviceZone.id, shipping_profile_id: shippingProfile.id, - fulfillment_provider_id: fulfillmentProvider.id, + provider_id: providerId, type: { code: "test", description: "test", @@ -767,7 +762,7 @@ moduleIntegrationTestRunner({ const shippingOptionData = generateCreateShippingOptionsData({ service_zone_id: serviceZone.id, shipping_profile_id: shippingProfile.id, - fulfillment_provider_id: providerId, + provider_id: providerId, }) const shippingOption = await service.createShippingOptions( @@ -812,7 +807,7 @@ moduleIntegrationTestRunner({ const shippingOptionData = generateCreateShippingOptionsData({ service_zone_id: serviceZone.id, shipping_profile_id: shippingProfile.id, - fulfillment_provider_id: providerId, + provider_id: providerId, }) const shippingOption = await service.createShippingOptions( @@ -858,17 +853,11 @@ moduleIntegrationTestRunner({ fulfillment_set_id: fulfillmentSet.id, }) - // service provider - const [{ id: providerId }] = - await MikroOrmWrapper.forkManager().execute( - "insert into fulfillment_provider (id) values ('sp_jdafwfleiwuonl') returning id" - ) - const shippingOption = await service.createShippingOptions( generateCreateShippingOptionsData({ service_zone_id: serviceZone.id, shipping_profile_id: shippingProfile.id, - fulfillment_provider_id: providerId, + provider_id: providerId, }) ) @@ -933,7 +922,7 @@ moduleIntegrationTestRunner({ generateCreateShippingOptionsData({ service_zone_id: serviceZone.id, shipping_profile_id: shippingProfile.id, - fulfillment_provider_id: providerId, + provider_id: providerId, }) ) diff --git a/packages/fulfillment/src/loaders/providers.ts b/packages/fulfillment/src/loaders/providers.ts index 1822fe7169..7e5f3572a0 100644 --- a/packages/fulfillment/src/loaders/providers.ts +++ b/packages/fulfillment/src/loaders/providers.ts @@ -2,9 +2,12 @@ import { moduleProviderLoader } from "@medusajs/modules-sdk" import { LoaderOptions, ModuleProvider, ModulesSdkTypes } from "@medusajs/types" import { asFunction, asValue, Lifetime } from "awilix" import { FulfillmentIdentifiersRegistrationName } from "@types" -import { lowerCaseFirst, promiseAll } from "@medusajs/utils" +import { + ContainerRegistrationKeys, + lowerCaseFirst, + promiseAll, +} from "@medusajs/utils" import { FulfillmentProviderService } from "@services" -import { ContainerRegistrationKeys } from "@medusajs/utils/src" const registrationFn = async (klass, container, pluginOptions) => { Object.entries(pluginOptions.config || []).map(([name, config]) => { @@ -105,6 +108,6 @@ async function syncDatabaseProviders({ container }) { await promiseAll(promises) } catch (error) { - logger.error(`Error syncing providers: ${error.message}`) + logger.error(`Error syncing the fulfillment providers: ${error.message}`) } } diff --git a/packages/fulfillment/src/migrations/Migration20240311145700_InitialSetupMigration.ts b/packages/fulfillment/src/migrations/Migration20240311145700_InitialSetupMigration.ts index 8f44b8812e..174e61ddd5 100644 --- a/packages/fulfillment/src/migrations/Migration20240311145700_InitialSetupMigration.ts +++ b/packages/fulfillment/src/migrations/Migration20240311145700_InitialSetupMigration.ts @@ -1,83 +1,603 @@ -import { Migration } from '@mikro-orm/migrations'; +import { Migration } from "@mikro-orm/migrations" export class Migration20240311145700_InitialSetupMigration extends Migration { - async up(): Promise { - this.addSql('create table if not exists "fulfillment_address" ("id" text not null, "company" text null, "first_name" text null, "last_name" text null, "address_1" text null, "address_2" text null, "city" text null, "country_code" text null, "province" text null, "postal_code" text null, "phone" text null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_address_pkey" primary key ("id"));'); - this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_address_deleted_at" ON "fulfillment_address" (deleted_at) WHERE deleted_at IS NOT NULL;'); + const shippingOptionTable = await this.execute( + `SELECT * FROM information_schema.tables where table_name = 'shipping_option' and table_schema = 'public';` + ) - this.addSql('create table if not exists "fulfillment_provider" ("id" text not null, "is_enabled" boolean not null default true, constraint "fulfillment_provider_pkey" primary key ("id"));'); - - this.addSql('create table if not exists "fulfillment_set" ("id" text not null, "name" text not null, "type" text not null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_set_pkey" primary key ("id"));'); - this.addSql('CREATE UNIQUE INDEX IF NOT EXISTS "IDX_fulfillment_set_name_unique" ON "fulfillment_set" (name) WHERE deleted_at IS NULL;'); - this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_set_deleted_at" ON "fulfillment_set" (deleted_at) WHERE deleted_at IS NOT NULL;'); - - this.addSql('create table if not exists "service_zone" ("id" text not null, "name" text not null, "metadata" jsonb null, "fulfillment_set_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "service_zone_pkey" primary key ("id"));'); - this.addSql('CREATE UNIQUE INDEX IF NOT EXISTS "IDX_service_zone_name_unique" ON "service_zone" (name) WHERE deleted_at IS NULL;'); - this.addSql('CREATE INDEX IF NOT EXISTS "IDX_service_zone_fulfillment_set_id" ON "service_zone" (fulfillment_set_id) WHERE deleted_at IS NULL;'); - this.addSql('CREATE INDEX IF NOT EXISTS "IDX_service_zone_deleted_at" ON "service_zone" (deleted_at) WHERE deleted_at IS NOT NULL;'); - - this.addSql('create table if not exists "geo_zone" ("id" text not null, "type" text check ("type" in (\'country\', \'province\', \'city\', \'zip\')) not null default \'country\', "country_code" text not null, "province_code" text null, "city" text null, "service_zone_id" text not null, "postal_expression" jsonb null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "geo_zone_pkey" primary key ("id"));'); - this.addSql('CREATE INDEX IF NOT EXISTS "IDX_geo_zone_country_code" ON "geo_zone" (country_code) WHERE deleted_at IS NULL;'); - this.addSql('CREATE INDEX IF NOT EXISTS "IDX_geo_zone_province_code" ON "geo_zone" (province_code) WHERE deleted_at IS NULL AND province_code IS NOT NULL;'); - this.addSql('CREATE INDEX IF NOT EXISTS "IDX_geo_zone_city" ON "geo_zone" (city) WHERE deleted_at IS NULL AND city IS NOT NULL;'); - this.addSql('CREATE INDEX IF NOT EXISTS "IDX_geo_zone_service_zone_id" ON "geo_zone" (service_zone_id) WHERE deleted_at IS NULL;'); - this.addSql('CREATE INDEX IF NOT EXISTS "IDX_geo_zone_deleted_at" ON "geo_zone" (deleted_at) WHERE deleted_at IS NOT NULL;'); - - this.addSql('create table if not exists "shipping_option_type" ("id" text not null, "label" text not null, "description" text null, "code" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "shipping_option_type_pkey" primary key ("id"));'); - this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_type_deleted_at" ON "shipping_option_type" (deleted_at) WHERE deleted_at IS NOT NULL;'); - - this.addSql('create table if not exists "shipping_profile" ("id" text not null, "name" text not null, "type" text not null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "shipping_profile_pkey" primary key ("id"));'); - this.addSql('CREATE UNIQUE INDEX IF NOT EXISTS "IDX_shipping_profile_name_unique" ON "shipping_profile" (name) WHERE deleted_at IS NULL;'); - this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_profile_deleted_at" ON "shipping_profile" (deleted_at) WHERE deleted_at IS NOT NULL;'); - - this.addSql('create table if not exists "shipping_option" ("id" text not null, "name" text not null, "price_type" text check ("price_type" in (\'calculated\', \'flat\')) not null default \'calculated\', "service_zone_id" text not null, "shipping_profile_id" text null, "fulfillment_provider_id" text null, "data" jsonb null, "metadata" jsonb null, "shipping_option_type_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "shipping_option_pkey" primary key ("id"));'); - this.addSql('alter table if exists "shipping_option" add constraint "shipping_option_shipping_option_type_id_unique" unique ("shipping_option_type_id");'); - this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_service_zone_id" ON "shipping_option" (service_zone_id) WHERE deleted_at IS NULL;'); - this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_shipping_profile_id" ON "shipping_option" (shipping_profile_id) WHERE deleted_at IS NULL;'); - this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_fulfillment_provider_id" ON "shipping_option" (fulfillment_provider_id) WHERE deleted_at IS NULL;'); - this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_shipping_option_type_id" ON "shipping_option" (shipping_option_type_id) WHERE deleted_at IS NULL;'); - this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_deleted_at" ON "shipping_option" (deleted_at) WHERE deleted_at IS NOT NULL;'); - - this.addSql('create table if not exists "shipping_option_rule" ("id" text not null, "attribute" text not null, "operator" text check ("operator" in (\'in\', \'eq\', \'ne\', \'gt\', \'gte\', \'lt\', \'lte\', \'nin\')) not null, "value" jsonb null, "shipping_option_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "shipping_option_rule_pkey" primary key ("id"));'); - this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_rule_shipping_option_id" ON "shipping_option_rule" (shipping_option_id) WHERE deleted_at IS NULL;'); - this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_rule_deleted_at" ON "shipping_option_rule" (deleted_at) WHERE deleted_at IS NOT NULL;'); - - this.addSql('create table if not exists "fulfillment" ("id" text not null, "location_id" text not null, "packed_at" timestamptz null, "shipped_at" timestamptz null, "delivered_at" timestamptz null, "canceled_at" timestamptz null, "data" jsonb null, "provider_id" text null, "shipping_option_id" text null, "metadata" jsonb null, "delivery_address_id" text null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_pkey" primary key ("id"));'); - this.addSql('alter table if exists "fulfillment" add constraint "fulfillment_delivery_address_id_unique" unique ("delivery_address_id");'); - this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_location_id" ON "fulfillment" (location_id) WHERE deleted_at IS NULL;'); - this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_provider_id" ON "fulfillment" (provider_id) WHERE deleted_at IS NULL;'); - this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_shipping_option_id" ON "fulfillment" (shipping_option_id) WHERE deleted_at IS NULL;'); - this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_deleted_at" ON "fulfillment" (deleted_at) WHERE deleted_at IS NOT NULL;'); - - this.addSql('create table if not exists "fulfillment_label" ("id" text not null, "tracking_number" text not null, "tracking_url" text not null, "label_url" text not null, "fulfillment_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_label_pkey" primary key ("id"));'); - this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_label_fulfillment_id" ON "fulfillment_label" (fulfillment_id) WHERE deleted_at IS NULL;'); - this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_label_deleted_at" ON "fulfillment_label" (deleted_at) WHERE deleted_at IS NOT NULL;'); - - this.addSql('create table if not exists "fulfillment_item" ("id" text not null, "title" text not null, "sku" text not null, "barcode" text not null, "quantity" numeric not null, "raw_quantity" jsonb not null, "line_item_id" text null, "inventory_item_id" text null, "fulfillment_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_item_pkey" primary key ("id"));'); - this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_item_line_item_id" ON "fulfillment_item" (line_item_id) WHERE deleted_at IS NULL;'); - this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_item_inventory_item_id" ON "fulfillment_item" (inventory_item_id) WHERE deleted_at IS NULL;'); - this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_item_fulfillment_id" ON "fulfillment_item" (fulfillment_id) WHERE deleted_at IS NULL;'); - this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_item_deleted_at" ON "fulfillment_item" (deleted_at) WHERE deleted_at IS NOT NULL;'); - - this.addSql('alter table if exists "service_zone" add constraint "service_zone_fulfillment_set_id_foreign" foreign key ("fulfillment_set_id") references "fulfillment_set" ("id") on update cascade on delete cascade;'); - - this.addSql('alter table if exists "geo_zone" add constraint "geo_zone_service_zone_id_foreign" foreign key ("service_zone_id") references "service_zone" ("id") on update cascade on delete cascade;'); - - this.addSql('alter table if exists "shipping_option" add constraint "shipping_option_service_zone_id_foreign" foreign key ("service_zone_id") references "service_zone" ("id") on update cascade on delete cascade;'); - this.addSql('alter table if exists "shipping_option" add constraint "shipping_option_shipping_profile_id_foreign" foreign key ("shipping_profile_id") references "shipping_profile" ("id") on update cascade on delete set null;'); - this.addSql('alter table if exists "shipping_option" add constraint "shipping_option_fulfillment_provider_id_foreign" foreign key ("fulfillment_provider_id") references "fulfillment_provider" ("id") on update cascade on delete set null;'); - this.addSql('alter table if exists "shipping_option" add constraint "shipping_option_shipping_option_type_id_foreign" foreign key ("shipping_option_type_id") references "shipping_option_type" ("id") on update cascade on delete cascade;'); - - this.addSql('alter table if exists "shipping_option_rule" add constraint "shipping_option_rule_shipping_option_id_foreign" foreign key ("shipping_option_id") references "shipping_option" ("id") on update cascade on delete cascade;'); - - this.addSql('alter table if exists "fulfillment" add constraint "fulfillment_provider_id_foreign" foreign key ("provider_id") references "fulfillment_provider" ("id") on update cascade on delete set null;'); - this.addSql('alter table if exists "fulfillment" add constraint "fulfillment_shipping_option_id_foreign" foreign key ("shipping_option_id") references "shipping_option" ("id") on update cascade on delete set null;'); - this.addSql('alter table if exists "fulfillment" add constraint "fulfillment_delivery_address_id_foreign" foreign key ("delivery_address_id") references "fulfillment_address" ("id") on update cascade on delete cascade;'); - - this.addSql('alter table if exists "fulfillment_label" add constraint "fulfillment_label_fulfillment_id_foreign" foreign key ("fulfillment_id") references "fulfillment" ("id") on update cascade on delete cascade;'); - - this.addSql('alter table if exists "fulfillment_item" add constraint "fulfillment_item_fulfillment_id_foreign" foreign key ("fulfillment_id") references "fulfillment" ("id") on update cascade on delete cascade;'); + if (!shippingOptionTable.length) { + await migrateUpModuleMigration.call(this) + } else { + await migrateUpBackwardCompatibility.call(this) + } } - +} + +/** + * This migration is for the initial setup of the fulfillment module in the case of + * an already existing database. It will check if the `shipping_option` table exists and in that case + * assume that all other associated tables exists and will migrate them all. + * + * This migration does not take into account the data migration part which + * should be handled separately. Also, after the data migration + * the tables should be cleaned up from the old columns. + **/ +async function migrateUpBackwardCompatibility( + this: Migration20240311145700_InitialSetupMigration +) { + this.addSql( + 'create table if not exists "fulfillment_address" ("id" text not null, "company" text null, "first_name" text null, "last_name" text null, "address_1" text null, "address_2" text null, "city" text null, "country_code" text null, "province" text null, "postal_code" text null, "phone" text null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_address_pkey" primary key ("id"));' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_address_deleted_at" ON "fulfillment_address" (deleted_at) WHERE deleted_at IS NOT NULL;' + ) + + this.addSql(` + alter table fulfillment_provider + alter column id type text using id::text; + + alter table fulfillment_provider + add is_enabled boolean default true not null; + `) + + this.addSql( + 'create table if not exists "fulfillment_set" ("id" text not null, "name" text not null, "type" text not null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_set_pkey" primary key ("id"));' + ) + this.addSql( + 'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_fulfillment_set_name_unique" ON "fulfillment_set" (name) WHERE deleted_at IS NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_set_deleted_at" ON "fulfillment_set" (deleted_at) WHERE deleted_at IS NOT NULL;' + ) + + this.addSql( + 'create table if not exists "service_zone" ("id" text not null, "name" text not null, "metadata" jsonb null, "fulfillment_set_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "service_zone_pkey" primary key ("id"));' + ) + this.addSql( + 'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_service_zone_name_unique" ON "service_zone" (name) WHERE deleted_at IS NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_service_zone_fulfillment_set_id" ON "service_zone" (fulfillment_set_id) WHERE deleted_at IS NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_service_zone_deleted_at" ON "service_zone" (deleted_at) WHERE deleted_at IS NOT NULL;' + ) + + this.addSql( + 'create table if not exists "geo_zone" ("id" text not null, "type" text check ("type" in (\'country\', \'province\', \'city\', \'zip\')) not null default \'country\', "country_code" text not null, "province_code" text null, "city" text null, "service_zone_id" text not null, "postal_expression" jsonb null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "geo_zone_pkey" primary key ("id"));' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_geo_zone_country_code" ON "geo_zone" (country_code) WHERE deleted_at IS NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_geo_zone_province_code" ON "geo_zone" (province_code) WHERE deleted_at IS NULL AND province_code IS NOT NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_geo_zone_city" ON "geo_zone" (city) WHERE deleted_at IS NULL AND city IS NOT NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_geo_zone_service_zone_id" ON "geo_zone" (service_zone_id) WHERE deleted_at IS NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_geo_zone_deleted_at" ON "geo_zone" (deleted_at) WHERE deleted_at IS NOT NULL;' + ) + + this.addSql( + 'create table if not exists "shipping_option_type" ("id" text not null, "label" text not null, "description" text null, "code" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "shipping_option_type_pkey" primary key ("id"));' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_shipping_option_type_deleted_at" ON "shipping_option_type" (deleted_at) WHERE deleted_at IS NOT NULL;' + ) + + this.addSql(` + alter table shipping_profile + alter column id type text using id::text; + + alter table shipping_profile + alter column name type text using name::text; + + alter table shipping_profile + alter column type type text using type::text; + + create unique index if not exists "IDX_shipping_profile_name_unique" + on shipping_profile (name) + where (deleted_at IS NULL); + + create index if not exists "IDX_shipping_profile_deleted_at" + on shipping_profile (deleted_at) + where (deleted_at IS NOT NULL); + `) + + this.addSql(` + alter table shipping_option + alter column id type text using id::text; + + alter table shipping_option + alter column name type text using name::text; + + alter table shipping_option + add service_zone_id text not null; + + alter table shipping_option + RENAME profile_id TO shipping_profile_id; + + alter table shipping_option + alter column region_id drop not null; + + alter table shipping_option + alter column price_type type text using price_type::text; + + alter table shipping_option + alter column price_type set default 'calculated'::text; + + alter table shipping_option + alter column data drop not null; + + alter table shipping_option + add shipping_option_type_id text not null; + + drop index if exists "IDX_5c58105f1752fca0f4ce69f466"; + + drop index if exists "IDX_c951439af4c98bf2bd7fb8726c"; + + drop index if exists "IDX_a0e206bfaed3cb63c186091734"; + + create index if not exists "IDX_shipping_option_service_zone_id" + on shipping_option (service_zone_id) + where (deleted_at IS NULL); + + create index if not exists "IDX_shipping_option_shipping_profile_id" + on shipping_option (shipping_profile_id) + where (deleted_at IS NULL); + + create index if not exists "IDX_shipping_option_provider_id" + on shipping_option (provider_id) + where (deleted_at IS NULL); + + create index if not exists "IDX_shipping_option_shipping_option_type_id" + on shipping_option (shipping_option_type_id) + where (deleted_at IS NULL); + + create index if not exists "IDX_shipping_option_deleted_at" + on shipping_option (deleted_at) + where (deleted_at IS NOT NULL); + + alter table shipping_option + add constraint shipping_option_shipping_option_type_id_unique + unique (shipping_option_type_id); + + alter table shipping_option + drop constraint "FK_5c58105f1752fca0f4ce69f4663"; + + alter table shipping_option + drop constraint "FK_c951439af4c98bf2bd7fb8726cd"; + + alter table shipping_option + drop constraint "FK_a0e206bfaed3cb63c1860917347"; + + alter table shipping_option + add constraint shipping_option_service_zone_id_foreign + foreign key (service_zone_id) references service_zone + on update cascade on delete cascade; + + alter table shipping_option + add constraint shipping_option_shipping_profile_id_foreign + foreign key (shipping_profile_id) references shipping_profile + on update cascade on delete set null; + + alter table shipping_option + add constraint shipping_option_provider_id_foreign + foreign key (provider_id) references fulfillment_provider + on update cascade on delete set null; + + alter table shipping_option + add constraint shipping_option_shipping_option_type_id_foreign + foreign key (shipping_option_type_id) references shipping_option_type + on update cascade on delete cascade; + + alter table shipping_option + drop constraint "CHK_7a367f5901ae0a5b0df75aee38"; + + alter table shipping_option + add constraint shipping_option_price_type_check + check (price_type = ANY (ARRAY ['calculated'::text, 'flat'::text])); + `) + + this.addSql( + 'create table if not exists "shipping_option_rule" ("id" text not null, "attribute" text not null, "operator" text check ("operator" in (\'in\', \'eq\', \'ne\', \'gt\', \'gte\', \'lt\', \'lte\', \'nin\')) not null, "value" jsonb null, "shipping_option_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "shipping_option_rule_pkey" primary key ("id"));' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_shipping_option_rule_shipping_option_id" ON "shipping_option_rule" (shipping_option_id) WHERE deleted_at IS NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_shipping_option_rule_deleted_at" ON "shipping_option_rule" (deleted_at) WHERE deleted_at IS NOT NULL;' + ) + + this.addSql(` + alter table fulfillment + alter + column id type text using id::text; + + alter table fulfillment + add packed_at timestamp with time zone; + + alter table fulfillment + alter column data drop not null; + + alter table fulfillment + add delivered_at timestamp with time zone; + + alter table fulfillment + add shipping_option_id text; + + alter table fulfillment + add delivery_address_id text; + + alter table fulfillment + alter + column provider_id type text using provider_id::text; + + alter table fulfillment + add deleted_at timestamp with time zone; + + alter table fulfillment + alter + column location_id type text using location_id::text; + + alter table fulfillment + alter column location_id set not null; + + drop index if exists "IDX_d73e55964e0ff2db8f03807d52"; + + drop index if exists "IDX_a52e234f729db789cf473297a5"; + + drop index if exists "IDX_f129acc85e346a10eed12b86fc"; + + drop index if exists "IDX_beb35a6de60a6c4f91d5ae57e4"; + + create index if not exists "IDX_fulfillment_location_id" + on fulfillment (location_id) where (deleted_at IS NULL); + + create index if not exists"IDX_fulfillment_provider_id" + on fulfillment (provider_id) where (deleted_at IS NULL); + + create index if not exists"IDX_fulfillment_shipping_option_id" + on fulfillment (shipping_option_id) where (deleted_at IS NULL); + + create index if not exists"IDX_fulfillment_deleted_at" + on fulfillment (deleted_at) where (deleted_at IS NOT NULL); + + alter table fulfillment + add constraint fulfillment_delivery_address_id_unique + unique (delivery_address_id); + + alter table fulfillment + drop + constraint "FK_a52e234f729db789cf473297a5c"; /* swap id */ + + alter table fulfillment + drop + constraint "FK_f129acc85e346a10eed12b86fca"; /* order id */ + + alter table fulfillment + drop + constraint "FK_beb35a6de60a6c4f91d5ae57e44"; /* provider id */ + + alter table fulfillment + drop + constraint "FK_d73e55964e0ff2db8f03807d52e"; /* claim id */ + + alter table fulfillment + add constraint fulfillment_provider_id_foreign + foreign key (provider_id) references fulfillment_provider + on update cascade on delete set null; + + alter table fulfillment + add constraint fulfillment_shipping_option_id_foreign + foreign key (shipping_option_id) references shipping_option + on update cascade on delete set null; + + alter table fulfillment + add constraint fulfillment_delivery_address_id_foreign + foreign key (delivery_address_id) references fulfillment_address + on update cascade on delete cascade; + `) + + this.addSql( + 'create table if not exists "fulfillment_label" ("id" text not null, "tracking_number" text not null, "tracking_url" text not null, "label_url" text not null, "fulfillment_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_label_pkey" primary key ("id"));' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_label_fulfillment_id" ON "fulfillment_label" (fulfillment_id) WHERE deleted_at IS NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_label_deleted_at" ON "fulfillment_label" (deleted_at) WHERE deleted_at IS NOT NULL;' + ) + + this.addSql(` + alter table fulfillment_item + add id text not null; + + alter table fulfillment_item + add title text not null; + + alter table fulfillment_item + add sku text not null; + + alter table fulfillment_item + add barcode text not null; + + alter table fulfillment_item + alter column quantity type numeric using quantity::numeric; + + alter table fulfillment_item + add raw_quantity jsonb not null; + + alter table fulfillment_item + add line_item_id text; + + alter table fulfillment_item + add inventory_item_id text; + + alter table fulfillment_item + alter column fulfillment_id type text using fulfillment_id::text; + + alter table fulfillment_item + add created_at timestamp with time zone default now() not null; + + alter table fulfillment_item + add updated_at timestamp with time zone default now() not null; + + alter table fulfillment_item + add deleted_at timestamp with time zone; + + create index if not exists "IDX_fulfillment_item_line_item_id" + on fulfillment_item (line_item_id) + where (deleted_at IS NULL); + + create index if not exists "IDX_fulfillment_item_inventory_item_id" + on fulfillment_item (inventory_item_id) + where (deleted_at IS NULL); + + create index if not exists "IDX_fulfillment_item_fulfillment_id" + on fulfillment_item (fulfillment_id) + where (deleted_at IS NULL); + + create index if not exists "IDX_fulfillment_item_deleted_at" + on fulfillment_item (deleted_at) + where (deleted_at IS NOT NULL); + + alter table fulfillment_item + drop constraint "FK_a033f83cc6bd7701a5687ab4b38"; /* fulfillment id */ + + alter table fulfillment_item + drop constraint "FK_e13ff60e74206b747a1896212d1"; /* item id */ + + alter table fulfillment_item + drop constraint "PK_bc3e8a388de75db146a249922e0"; /* item id + fulfillment id PK */ + + alter table fulfillment_item + alter column item_id drop not null; + + alter table fulfillment_item + add constraint fulfillment_item_fulfillment_id_foreign + foreign key (fulfillment_id) references fulfillment + on update cascade on delete cascade; + + alter table "fulfillment_item" add primary key ("id"); + `) + + this.addSql( + 'alter table if exists "service_zone" add constraint "service_zone_fulfillment_set_id_foreign" foreign key ("fulfillment_set_id") references "fulfillment_set" ("id") on update cascade on delete cascade;' + ) + + this.addSql( + 'alter table if exists "geo_zone" add constraint "geo_zone_service_zone_id_foreign" foreign key ("service_zone_id") references "service_zone" ("id") on update cascade on delete cascade;' + ) + + this.addSql( + 'alter table if exists "shipping_option_rule" add constraint "shipping_option_rule_shipping_option_id_foreign" foreign key ("shipping_option_id") references "shipping_option" ("id") on update cascade on delete cascade;' + ) + + this.addSql( + 'alter table if exists "fulfillment_label" add constraint "fulfillment_label_fulfillment_id_foreign" foreign key ("fulfillment_id") references "fulfillment" ("id") on update cascade on delete cascade;' + ) +} + +/** + * This migration is for be initial setup of the fulfillment module in the case of + * a new database. It will create all the necessary tables and indexes. + */ +async function migrateUpModuleMigration( + this: Migration20240311145700_InitialSetupMigration +) { + this.addSql( + 'create table if not exists "fulfillment_address" ("id" text not null, "company" text null, "first_name" text null, "last_name" text null, "address_1" text null, "address_2" text null, "city" text null, "country_code" text null, "province" text null, "postal_code" text null, "phone" text null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_address_pkey" primary key ("id"));' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_address_deleted_at" ON "fulfillment_address" (deleted_at) WHERE deleted_at IS NOT NULL;' + ) + + this.addSql( + 'create table if not exists "fulfillment_provider" ("id" text not null, "is_enabled" boolean not null default true, constraint "fulfillment_provider_pkey" primary key ("id"));' + ) + + this.addSql( + 'create table if not exists "fulfillment_set" ("id" text not null, "name" text not null, "type" text not null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_set_pkey" primary key ("id"));' + ) + this.addSql( + 'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_fulfillment_set_name_unique" ON "fulfillment_set" (name) WHERE deleted_at IS NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_set_deleted_at" ON "fulfillment_set" (deleted_at) WHERE deleted_at IS NOT NULL;' + ) + + this.addSql( + 'create table if not exists "service_zone" ("id" text not null, "name" text not null, "metadata" jsonb null, "fulfillment_set_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "service_zone_pkey" primary key ("id"));' + ) + this.addSql( + 'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_service_zone_name_unique" ON "service_zone" (name) WHERE deleted_at IS NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_service_zone_fulfillment_set_id" ON "service_zone" (fulfillment_set_id) WHERE deleted_at IS NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_service_zone_deleted_at" ON "service_zone" (deleted_at) WHERE deleted_at IS NOT NULL;' + ) + + this.addSql( + 'create table if not exists "geo_zone" ("id" text not null, "type" text check ("type" in (\'country\', \'province\', \'city\', \'zip\')) not null default \'country\', "country_code" text not null, "province_code" text null, "city" text null, "service_zone_id" text not null, "postal_expression" jsonb null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "geo_zone_pkey" primary key ("id"));' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_geo_zone_country_code" ON "geo_zone" (country_code) WHERE deleted_at IS NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_geo_zone_province_code" ON "geo_zone" (province_code) WHERE deleted_at IS NULL AND province_code IS NOT NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_geo_zone_city" ON "geo_zone" (city) WHERE deleted_at IS NULL AND city IS NOT NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_geo_zone_service_zone_id" ON "geo_zone" (service_zone_id) WHERE deleted_at IS NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_geo_zone_deleted_at" ON "geo_zone" (deleted_at) WHERE deleted_at IS NOT NULL;' + ) + + this.addSql( + 'create table if not exists "shipping_option_type" ("id" text not null, "label" text not null, "description" text null, "code" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "shipping_option_type_pkey" primary key ("id"));' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_shipping_option_type_deleted_at" ON "shipping_option_type" (deleted_at) WHERE deleted_at IS NOT NULL;' + ) + + this.addSql( + 'create table if not exists "shipping_profile" ("id" text not null, "name" text not null, "type" text not null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "shipping_profile_pkey" primary key ("id"));' + ) + this.addSql( + 'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_shipping_profile_name_unique" ON "shipping_profile" (name) WHERE deleted_at IS NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_shipping_profile_deleted_at" ON "shipping_profile" (deleted_at) WHERE deleted_at IS NOT NULL;' + ) + + this.addSql( + 'create table if not exists "shipping_option" ("id" text not null, "name" text not null, "price_type" text check ("price_type" in (\'calculated\', \'flat\')) not null default \'flat\', "service_zone_id" text not null, "shipping_profile_id" text null, "provider_id" text null, "data" jsonb null, "metadata" jsonb null, "shipping_option_type_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "shipping_option_pkey" primary key ("id"));' + ) + this.addSql( + 'alter table if exists "shipping_option" add constraint "shipping_option_shipping_option_type_id_unique" unique ("shipping_option_type_id");' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_shipping_option_service_zone_id" ON "shipping_option" (service_zone_id) WHERE deleted_at IS NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_shipping_option_shipping_profile_id" ON "shipping_option" (shipping_profile_id) WHERE deleted_at IS NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_shipping_option_provider_id" ON "shipping_option" (provider_id) WHERE deleted_at IS NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_shipping_option_shipping_option_type_id" ON "shipping_option" (shipping_option_type_id) WHERE deleted_at IS NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_shipping_option_deleted_at" ON "shipping_option" (deleted_at) WHERE deleted_at IS NOT NULL;' + ) + + this.addSql( + 'create table if not exists "shipping_option_rule" ("id" text not null, "attribute" text not null, "operator" text check ("operator" in (\'in\', \'eq\', \'ne\', \'gt\', \'gte\', \'lt\', \'lte\', \'nin\')) not null, "value" jsonb null, "shipping_option_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "shipping_option_rule_pkey" primary key ("id"));' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_shipping_option_rule_shipping_option_id" ON "shipping_option_rule" (shipping_option_id) WHERE deleted_at IS NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_shipping_option_rule_deleted_at" ON "shipping_option_rule" (deleted_at) WHERE deleted_at IS NOT NULL;' + ) + + this.addSql( + 'create table if not exists "fulfillment" ("id" text not null, "location_id" text not null, "packed_at" timestamptz null, "shipped_at" timestamptz null, "delivered_at" timestamptz null, "canceled_at" timestamptz null, "data" jsonb null, "provider_id" text null, "shipping_option_id" text null, "metadata" jsonb null, "delivery_address_id" text null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_pkey" primary key ("id"));' + ) + this.addSql( + 'alter table if exists "fulfillment" add constraint "fulfillment_delivery_address_id_unique" unique ("delivery_address_id");' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_location_id" ON "fulfillment" (location_id) WHERE deleted_at IS NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_provider_id" ON "fulfillment" (provider_id) WHERE deleted_at IS NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_shipping_option_id" ON "fulfillment" (shipping_option_id) WHERE deleted_at IS NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_deleted_at" ON "fulfillment" (deleted_at) WHERE deleted_at IS NOT NULL;' + ) + + this.addSql( + 'create table if not exists "fulfillment_label" ("id" text not null, "tracking_number" text not null, "tracking_url" text not null, "label_url" text not null, "fulfillment_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_label_pkey" primary key ("id"));' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_label_fulfillment_id" ON "fulfillment_label" (fulfillment_id) WHERE deleted_at IS NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_label_deleted_at" ON "fulfillment_label" (deleted_at) WHERE deleted_at IS NOT NULL;' + ) + + this.addSql( + 'create table if not exists "fulfillment_item" ("id" text not null, "title" text not null, "sku" text not null, "barcode" text not null, "quantity" numeric not null, "raw_quantity" jsonb not null, "line_item_id" text null, "inventory_item_id" text null, "fulfillment_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_item_pkey" primary key ("id"));' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_item_line_item_id" ON "fulfillment_item" (line_item_id) WHERE deleted_at IS NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_item_inventory_item_id" ON "fulfillment_item" (inventory_item_id) WHERE deleted_at IS NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_item_fulfillment_id" ON "fulfillment_item" (fulfillment_id) WHERE deleted_at IS NULL;' + ) + this.addSql( + 'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_item_deleted_at" ON "fulfillment_item" (deleted_at) WHERE deleted_at IS NOT NULL;' + ) + + this.addSql( + 'alter table if exists "service_zone" add constraint "service_zone_fulfillment_set_id_foreign" foreign key ("fulfillment_set_id") references "fulfillment_set" ("id") on update cascade on delete cascade;' + ) + + this.addSql( + 'alter table if exists "geo_zone" add constraint "geo_zone_service_zone_id_foreign" foreign key ("service_zone_id") references "service_zone" ("id") on update cascade on delete cascade;' + ) + + this.addSql( + 'alter table if exists "shipping_option" add constraint "shipping_option_service_zone_id_foreign" foreign key ("service_zone_id") references "service_zone" ("id") on update cascade on delete cascade;' + ) + this.addSql( + 'alter table if exists "shipping_option" add constraint "shipping_option_shipping_profile_id_foreign" foreign key ("shipping_profile_id") references "shipping_profile" ("id") on update cascade on delete set null;' + ) + this.addSql( + 'alter table if exists "shipping_option" add constraint "shipping_option_provider_id_foreign" foreign key ("provider_id") references "fulfillment_provider" ("id") on update cascade on delete set null;' + ) + this.addSql( + 'alter table if exists "shipping_option" add constraint "shipping_option_shipping_option_type_id_foreign" foreign key ("shipping_option_type_id") references "shipping_option_type" ("id") on update cascade on delete cascade;' + ) + + this.addSql( + 'alter table if exists "shipping_option_rule" add constraint "shipping_option_rule_shipping_option_id_foreign" foreign key ("shipping_option_id") references "shipping_option" ("id") on update cascade on delete cascade;' + ) + + this.addSql( + 'alter table if exists "fulfillment" add constraint "fulfillment_provider_id_foreign" foreign key ("provider_id") references "fulfillment_provider" ("id") on update cascade on delete set null;' + ) + this.addSql( + 'alter table if exists "fulfillment" add constraint "fulfillment_shipping_option_id_foreign" foreign key ("shipping_option_id") references "shipping_option" ("id") on update cascade on delete set null;' + ) + this.addSql( + 'alter table if exists "fulfillment" add constraint "fulfillment_delivery_address_id_foreign" foreign key ("delivery_address_id") references "fulfillment_address" ("id") on update cascade on delete cascade;' + ) + + this.addSql( + 'alter table if exists "fulfillment_label" add constraint "fulfillment_label_fulfillment_id_foreign" foreign key ("fulfillment_id") references "fulfillment" ("id") on update cascade on delete cascade;' + ) + + this.addSql( + 'alter table if exists "fulfillment_item" add constraint "fulfillment_item_fulfillment_id_foreign" foreign key ("fulfillment_id") references "fulfillment" ("id") on update cascade on delete cascade;' + ) } diff --git a/packages/fulfillment/src/models/shipping-option.ts b/packages/fulfillment/src/models/shipping-option.ts index 549c692626..e939f60278 100644 --- a/packages/fulfillment/src/models/shipping-option.ts +++ b/packages/fulfillment/src/models/shipping-option.ts @@ -50,7 +50,7 @@ const ShippingProfileIdIndex = createPsqlIndexStatementHelper({ const FulfillmentProviderIdIndex = createPsqlIndexStatementHelper({ tableName: "shipping_option", - columns: "fulfillment_provider_id", + columns: "provider_id", where: "deleted_at IS NULL", }) @@ -73,7 +73,7 @@ export default class ShippingOption { @Enum({ items: () => ShippingOptionPriceType, - default: ShippingOptionPriceType.CALCULATED, + default: ShippingOptionPriceType.FLAT, }) price_type: ShippingOptionPriceType @@ -98,12 +98,12 @@ export default class ShippingOption { @ManyToOne(() => FulfillmentProvider, { type: "text", - fieldName: "fulfillment_provider_id", + fieldName: "provider_id", mapToPk: true, nullable: true, }) @FulfillmentProviderIdIndex.MikroORMIndex() - fulfillment_provider_id: string + provider_id: string @Property({ columnType: "text", persist: false }) @ShippingOptionTypeIdIndex.MikroORMIndex() @@ -126,7 +126,7 @@ export default class ShippingOption { @ManyToOne(() => FulfillmentProvider, { persist: false, }) - fulfillment_provider: FulfillmentProvider | null + provider: FulfillmentProvider | null @OneToOne(() => ShippingOptionType, (so) => so.shipping_option, { owner: true, diff --git a/packages/fulfillment/src/module-definition.ts b/packages/fulfillment/src/module-definition.ts index 9bbb2cdea9..4f130ce7dd 100644 --- a/packages/fulfillment/src/module-definition.ts +++ b/packages/fulfillment/src/module-definition.ts @@ -2,7 +2,6 @@ import { ModuleExports } from "@medusajs/types" import * as ModuleServices from "@services" import { FulfillmentModuleService } from "@services" import { Modules } from "@medusajs/modules-sdk" -import * as Models from "@models" import * as ModuleModels from "@models" import { ModulesSdkUtils } from "@medusajs/utils" import * as ModuleRepositories from "@repositories" @@ -10,7 +9,7 @@ import loadProviders from "./loaders/providers" const migrationScriptOptions = { moduleName: Modules.FULFILLMENT, - models: Models, + models: ModuleModels, pathToMigrations: __dirname + "/migrations", } @@ -30,7 +29,7 @@ const containerLoader = ModulesSdkUtils.moduleContainerLoaderFactory({ const connectionLoader = ModulesSdkUtils.mikroOrmConnectionLoaderFactory({ moduleName: Modules.FULFILLMENT, - moduleModels: Object.values(Models), + moduleModels: Object.values(ModuleModels), migrationsPath: __dirname + "/migrations", }) diff --git a/packages/fulfillment/src/services/fulfillment-module-service.ts b/packages/fulfillment/src/services/fulfillment-module-service.ts index c84a0bb1a1..3c7e681ff1 100644 --- a/packages/fulfillment/src/services/fulfillment-module-service.ts +++ b/packages/fulfillment/src/services/fulfillment-module-service.ts @@ -143,7 +143,7 @@ export default class FulfillmentModuleService< "rules", "type", "shipping_profile", - "fulfillment_provider", + "provider", ...(normalizedConfig.relations ?? []), ] // The assumption is that there won't be an infinite amount of shipping options. So if a context filtering needs to be applied we can retrieve them all. diff --git a/packages/fulfillment/src/types/index.ts b/packages/fulfillment/src/types/index.ts index bae7407b28..758ea00de6 100644 --- a/packages/fulfillment/src/types/index.ts +++ b/packages/fulfillment/src/types/index.ts @@ -1,4 +1,9 @@ -import { IEventBusModuleService, Logger } from "@medusajs/types" +import { + IEventBusModuleService, + Logger, + ModuleProviderExports, + ModuleServiceInitializeOptions, +} from "@medusajs/types" export type InitializeModuleInjectableDependencies = { logger?: Logger @@ -7,3 +12,22 @@ export type InitializeModuleInjectableDependencies = { export const FulfillmentIdentifiersRegistrationName = "fulfillment_providers_identifier" + +export type FulfillmentModuleOptions = + Partial & { + /** + * Providers to be registered + */ + providers?: { + /** + * The module provider to be registered + */ + resolve: string | ModuleProviderExports + options: { + /** + * key value pair of the provider name and the configuration to be passed to the provider constructor + */ + config: Record + } + }[] + } diff --git a/packages/types/src/fulfillment/common/shipping-option.ts b/packages/types/src/fulfillment/common/shipping-option.ts index 07b2b166cf..b76403b20b 100644 --- a/packages/types/src/fulfillment/common/shipping-option.ts +++ b/packages/types/src/fulfillment/common/shipping-option.ts @@ -20,7 +20,7 @@ export interface ShippingOptionDTO { price_type: ShippingOptionPriceType service_zone_id: string shipping_profile_id: string - fulfillment_provider_id: string + provider_id: string shipping_option_type_id: string | null data: Record | null metadata: Record | null diff --git a/packages/types/src/fulfillment/mutations/shipping-option.ts b/packages/types/src/fulfillment/mutations/shipping-option.ts index 0793143273..8a2d01849f 100644 --- a/packages/types/src/fulfillment/mutations/shipping-option.ts +++ b/packages/types/src/fulfillment/mutations/shipping-option.ts @@ -7,7 +7,7 @@ export interface CreateShippingOptionDTO { price_type: ShippingOptionPriceType service_zone_id: string shipping_profile_id: string - fulfillment_provider_id: string + provider_id: string type: Omit data?: Record | null rules?: Omit[] @@ -19,7 +19,7 @@ export interface UpdateShippingOptionDTO { price_type?: ShippingOptionPriceType service_zone_id?: string shipping_profile_id?: string - fulfillment_provider_id?: string + provider_id?: string type: Omit | { id: string } data?: Record | null rules?: (