From a22d873aa5e4aa82a1b0a2acd601f26b0095d119 Mon Sep 17 00:00:00 2001 From: adrien2p Date: Mon, 12 Feb 2024 13:43:27 +0100 Subject: [PATCH 01/29] feat(fulfillment): Module sevice implemntation first iteration --- .../fulfillment-module-service.spec.ts | 176 ++++++++++++++++++ .../integration-tests/__tests__/index.ts | 5 - .../integration-tests/utils/index.ts | 1 + .../fulfillment/src/models/fullfilment-set.ts | 13 ++ .../fulfillment/src/models/service-zone.ts | 13 ++ .../src/repositories/fulfillment-set.ts | 57 ++++++ .../services/fulfillment-module-service.ts | 145 ++++++++++++++- .../fulfillment/mutations/fulfillment-set.ts | 2 +- .../src/fulfillment/mutations/geo-zone.ts | 1 - .../src/fulfillment/mutations/service-zone.ts | 8 +- .../src/common/create-psql-index-helper.ts | 6 +- 11 files changed, 406 insertions(+), 21 deletions(-) create mode 100644 packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts delete mode 100644 packages/fulfillment/integration-tests/__tests__/index.ts create mode 100644 packages/fulfillment/src/repositories/fulfillment-set.ts diff --git a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts new file mode 100644 index 0000000000000..7ab2fc90c1afb --- /dev/null +++ b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts @@ -0,0 +1,176 @@ +import { Modules } from "@medusajs/modules-sdk" +import { initModules } from "medusa-test-utils/dist" +import { + CreateFulfillmentSetDTO, + IFulfillmentModuleService, +} from "@medusajs/types" +import { SqlEntityManager } from "@mikro-orm/postgresql" +import { getInitModuleConfig, MikroOrmWrapper } from "../utils" + +describe("fulfillment module service", function () { + let service: IFulfillmentModuleService + let testManager: SqlEntityManager + let shutdownFunc: () => Promise + + beforeAll(async () => { + const initModulesConfig = getInitModuleConfig() + + const { medusaApp, shutdown } = await initModules(initModulesConfig) + + service = medusaApp.modules[Modules.FULFILLMENT] + + shutdownFunc = shutdown + }) + + beforeEach(async () => { + await MikroOrmWrapper.setupDatabase() + testManager = MikroOrmWrapper.forkManager() + }) + + afterEach(async () => { + await MikroOrmWrapper.clearDatabase() + }) + + afterAll(async () => { + await shutdownFunc() + }) + + it("should create a new fulfillment set", async function () { + const data: CreateFulfillmentSetDTO = { + name: "test", + type: "test-type", + } + + const fulfillmentSets = await service.create(data) + + expect(fulfillmentSets).toHaveLength(1) + expect(fulfillmentSets[0]).toEqual( + expect.objectContaining({ + id: expect.any(String), + name: data.name, + type: data.type, + }) + ) + }) + + it("should create a collection of fulfillment sets", async function () { + const data = [ + { + name: "test", + type: "test-type", + }, + { + name: "test2", + type: "test-type2", + }, + ] + + const fulfillmentSets = await service.create(data) + + expect(fulfillmentSets).toHaveLength(2) + + let i = 0 + for (const data_ of data) { + expect(fulfillmentSets[i]).toEqual( + expect.objectContaining({ + id: expect.any(String), + name: data_.name, + type: data_.type, + }) + ) + ++i + } + }) + + it("should create a new fulfillment set with new service zones", async function () { + const data = { + name: "test", + type: "test-type", + service_zones: [ + { + name: "test", + }, + ], + } + + const fulfillmentSets = await service.create(data) + + expect(fulfillmentSets).toHaveLength(1) + expect(fulfillmentSets[0]).toEqual( + expect.objectContaining({ + id: expect.any(String), + name: data.name, + type: data.type, + service_zones: expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + name: data.service_zones[0].name, + }), + ]), + }) + ) + }) + + it("should create a collection of fulfillment sets with new service zones", async function () { + const data = [ + { + name: "test", + type: "test-type", + service_zones: [ + { + name: "test", + }, + ], + }, + { + name: "test2", + type: "test-type2", + service_zones: [ + { + name: "test", + }, + ], + }, + { + name: "test3", + type: "test-type3", + service_zones: [ + { + name: "test2", + }, + ], + }, + ] + + const fulfillmentSets = await service.create(data) + + expect(fulfillmentSets).toHaveLength(3) + + let i = 0 + for (const data_ of data) { + expect(fulfillmentSets[i]).toEqual( + expect.objectContaining({ + id: expect.any(String), + name: data_.name, + type: data_.type, + service_zones: expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + name: data_.service_zones[0].name, + }), + ]), + }) + ) + ++i + } + + // The two first fulfillment sets share the same service zone + expect(fulfillmentSets[0].service_zones[0].id).toEqual( + fulfillmentSets[1].service_zones[0].id + ) + }) + + it("should create a collection of fulfillment sets with new service zones and existing service zones", async function () { + // TODO + }) +}) diff --git a/packages/fulfillment/integration-tests/__tests__/index.ts b/packages/fulfillment/integration-tests/__tests__/index.ts deleted file mode 100644 index 333c84c1ddbf2..0000000000000 --- a/packages/fulfillment/integration-tests/__tests__/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -describe("noop", function () { - it("should run", function () { - expect(true).toBe(true) - }) -}) diff --git a/packages/fulfillment/integration-tests/utils/index.ts b/packages/fulfillment/integration-tests/utils/index.ts index 6b917ed30e5e7..ba28fb552380b 100644 --- a/packages/fulfillment/integration-tests/utils/index.ts +++ b/packages/fulfillment/integration-tests/utils/index.ts @@ -1 +1,2 @@ export * from "./database" +export * from "./get-init-module-config" diff --git a/packages/fulfillment/src/models/fullfilment-set.ts b/packages/fulfillment/src/models/fullfilment-set.ts index 54eaaf5f3c7c5..35e22fbe151e4 100644 --- a/packages/fulfillment/src/models/fullfilment-set.ts +++ b/packages/fulfillment/src/models/fullfilment-set.ts @@ -29,6 +29,15 @@ const deletedAtIndexStatement = createPsqlIndexStatementHelper({ where: "deleted_at IS NOT NULL", }) +const nameIndexName = "IDX_fulfillment_set_name_unique" +const nameIndexStatement = createPsqlIndexStatementHelper({ + name: nameIndexName, + tableName: "fulfillment_set", + columns: "name", + unique: true, + where: "deleted_at IS NOT NULL", +}) + @Entity() @Filter(DALUtils.mikroOrmSoftDeletableFilterOptions) export default class FulfillmentSet { @@ -38,6 +47,10 @@ export default class FulfillmentSet { id: string @Property({ columnType: "text" }) + @Index({ + name: nameIndexName, + expression: nameIndexStatement, + }) name: string @Property({ columnType: "text" }) diff --git a/packages/fulfillment/src/models/service-zone.ts b/packages/fulfillment/src/models/service-zone.ts index 415570e53f1a4..9a249f0a1fc24 100644 --- a/packages/fulfillment/src/models/service-zone.ts +++ b/packages/fulfillment/src/models/service-zone.ts @@ -32,6 +32,15 @@ const deletedAtIndexStatement = createPsqlIndexStatementHelper({ where: "deleted_at IS NOT NULL", }) +const nameIndexName = "IDX_service_zone_name_unique" +const nameIndexStatement = createPsqlIndexStatementHelper({ + name: nameIndexName, + tableName: "service_zone", + columns: "name", + unique: true, + where: "deleted_at IS NOT NULL", +}) + @Entity() @Filter(DALUtils.mikroOrmSoftDeletableFilterOptions) export default class ServiceZone { @@ -41,6 +50,10 @@ export default class ServiceZone { id: string @Property({ columnType: "text" }) + @Index({ + name: nameIndexName, + expression: nameIndexStatement, + }) name: string @Property({ columnType: "jsonb", nullable: true }) diff --git a/packages/fulfillment/src/repositories/fulfillment-set.ts b/packages/fulfillment/src/repositories/fulfillment-set.ts new file mode 100644 index 0000000000000..b90df6ed0c5e9 --- /dev/null +++ b/packages/fulfillment/src/repositories/fulfillment-set.ts @@ -0,0 +1,57 @@ +import { Context, FulfillmentTypes } from "@medusajs/types" +import { DALUtils, promiseAll } from "@medusajs/utils" +import { SqlEntityManager } from "@mikro-orm/postgresql" +import { FulfillmentSet, ServiceZone } from "@models" + +interface CreateFulfillmentSetDTO + extends FulfillmentTypes.CreateFulfillmentSetDTO { + service_zones: { id: string; name: string }[] +} + +export class FulfillmentSetRepository extends DALUtils.mikroOrmBaseRepositoryFactory( + FulfillmentSet +) { + async create( + data: CreateFulfillmentSetDTO[], + context: Context = {} + ): Promise { + const manager = this.getActiveManager(context) + + const fulfillmentSets = await promiseAll( + data.map(async (fulfillmentSetData) => { + const { service_zones, ...fulfillmentSetDataOnly } = fulfillmentSetData + const fulfillmentSet = manager.create( + FulfillmentSet, + fulfillmentSetDataOnly + ) + + // Manager the many to many between the relationship + if (service_zones?.length) { + await fulfillmentSet.service_zones.init({ populate: true }) + const fulfillmentSetServiceZones = new Set( + fulfillmentSet.service_zones.getItems().map(({ id }) => id) + ) + + const serviceZoneToAttach = service_zones + .filter(({ id }) => !fulfillmentSetServiceZones.has(id)) + .map(({ id }) => ({ id })) + const serviceZoneToDetach = fulfillmentSet.service_zones + .getItems() + .filter(({ id }) => !service_zones.some((s) => s.id === id)) + + fulfillmentSet.service_zones.add( + serviceZoneToAttach.map(({ id }) => + manager.create(ServiceZone, { id } as any) + ) + ) + fulfillmentSet.service_zones.remove(serviceZoneToDetach) + } + + manager.persist(fulfillmentSet) + return fulfillmentSet + }) + ) + + return fulfillmentSets + } +} diff --git a/packages/fulfillment/src/services/fulfillment-module-service.ts b/packages/fulfillment/src/services/fulfillment-module-service.ts index 908de3fd3e190..096f5ddd7778a 100644 --- a/packages/fulfillment/src/services/fulfillment-module-service.ts +++ b/packages/fulfillment/src/services/fulfillment-module-service.ts @@ -11,17 +11,21 @@ import { import { InjectTransactionManager, ModulesSdkUtils } from "@medusajs/utils" import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config" -import { FulfillmentSet, ServiceZone, ShippingOption } from "@models" +import { FulfillmentSet, GeoZone, ServiceZone, ShippingOption } from "@models" const generateMethodForModels = [ServiceZone, ShippingOption] type InjectedDependencies = { baseRepository: DAL.RepositoryService - fulfillmentService: ModulesSdkTypes.InternalModuleService + fulfillmentSetService: ModulesSdkTypes.InternalModuleService + serviceZoneService: ModulesSdkTypes.InternalModuleService + geoZoneService: ModulesSdkTypes.InternalModuleService } export default class FulfillmentModuleService< - TEntity extends FulfillmentSet = FulfillmentSet + TEntity extends FulfillmentSet = FulfillmentSet, + TServiceZoneEntity extends ServiceZone = ServiceZone, + TGeoZoneEntity extends GeoZone = GeoZone > extends ModulesSdkUtils.abstractModuleServiceFactory< InjectedDependencies, @@ -35,16 +39,25 @@ export default class FulfillmentModuleService< implements IFulfillmentModuleService { protected baseRepository_: DAL.RepositoryService - protected readonly fulfillmentService_: ModulesSdkTypes.InternalModuleService + protected readonly fulfillmentSetService_: ModulesSdkTypes.InternalModuleService + protected readonly serviceZoneService_: ModulesSdkTypes.InternalModuleService + protected readonly geoZoneService_: ModulesSdkTypes.InternalModuleService constructor( - { baseRepository, fulfillmentService }: InjectedDependencies, + { + baseRepository, + fulfillmentSetService, + serviceZoneService, + geoZoneService, + }: InjectedDependencies, protected readonly moduleDeclaration: InternalModuleDeclaration ) { // @ts-ignore super(...arguments) this.baseRepository_ = baseRepository - this.fulfillmentService_ = fulfillmentService + this.fulfillmentSetService_ = fulfillmentSetService + this.serviceZoneService_ = serviceZoneService + this.geoZoneService_ = geoZoneService } __joinerConfig(): ModuleJoinerConfig { @@ -69,7 +82,125 @@ export default class FulfillmentModuleService< ): Promise< FulfillmentTypes.FulfillmentSetDTO | FulfillmentTypes.FulfillmentSetDTO[] > { - return [] + const data_: FulfillmentTypes.CreateFulfillmentSetDTO[] = Array.isArray( + data + ) + ? data + : [data] + + const fulfillmentSetMap = new Map< + string, + FulfillmentTypes.CreateFulfillmentSetDTO + >() + + const fulfillmentSetServiceZonesMap = new Map< + string, + Map< + string, + Required["service_zones"][number] + > + >() + + const serviceZoneToCreate: FulfillmentTypes.CreateServiceZoneDTO[] = [] + + const serviceZoneIds = data_ + .map(({ service_zones }) => service_zones?.map(({ id }: any) => id)) + .flat() + .filter(Boolean) + + let existingServiceZones: TServiceZoneEntity[] = [] + let existingServiceZonesMap = new Map() + if (serviceZoneIds.length) { + existingServiceZones = await this.serviceZoneService_.list( + { + id: serviceZoneIds, + }, + { + select: ["id", "name"], + }, + sharedContext + ) + + existingServiceZonesMap = new Map( + existingServiceZones.map((serviceZone) => [serviceZone.id, serviceZone]) + ) + } + + data_.forEach(({ service_zones, ...fulfillmentSetDataOnly }) => { + fulfillmentSetMap.set(fulfillmentSetDataOnly.name, fulfillmentSetDataOnly) + + /** + * If there is any service zone to process + * store the service zones to create while populating the fulfillment set service zone map + * in order to be able after creating the service zones to re update the map with the + * newly create service zones and then assign them to the fulfillment sets to be + * to create. + */ + + // TODO manage the geo zones as well + + if (service_zones?.length) { + const serviceZoneTuple: [ + string, + Required["service_zones"][number] + ][] = service_zones.map((serviceZone) => { + let existingZone = + "id" in serviceZone + ? existingServiceZonesMap.get(serviceZone.id)! + : null + if (!("id" in serviceZone)) { + serviceZoneToCreate.push(serviceZone) + } + + const serviceZoneIdentifier = + "id" in serviceZone ? serviceZone.id : serviceZone.name + + return [serviceZoneIdentifier, existingZone ?? serviceZone] + }) + + fulfillmentSetServiceZonesMap.set( + fulfillmentSetDataOnly.name, + new Map(serviceZoneTuple) + ) + } + }) + + if (serviceZoneToCreate.length) { + const createdServiceZones = await this.serviceZoneService_.create( + serviceZoneToCreate, + sharedContext + ) + const createdServiceZoneMap = new Map( + createdServiceZones.map((serviceZone: ServiceZone) => [ + serviceZone.name, + serviceZone, + ]) + ) + + for (const [ + fulfillmentSetName, + serviceZoneToCreateMap, + ] of fulfillmentSetServiceZonesMap) { + ;[...createdServiceZoneMap.values()].forEach((serviceZone) => { + if (serviceZoneToCreateMap.has(serviceZone.name)) { + serviceZoneToCreateMap.set(serviceZone.name, serviceZone) + } + }) + + const fulfillmentSet = fulfillmentSetMap.get(fulfillmentSetName)! + fulfillmentSet.service_zones = [...serviceZoneToCreateMap.values()] + fulfillmentSetMap.set(fulfillmentSetName, fulfillmentSet) + } + } + + const createdFulfillmentSets = await this.fulfillmentSetService_.create( + [...fulfillmentSetMap.values()], + sharedContext + ) + + return await this.baseRepository_.serialize(createdFulfillmentSets, { + populate: true, + }) } createServiceZones( diff --git a/packages/types/src/fulfillment/mutations/fulfillment-set.ts b/packages/types/src/fulfillment/mutations/fulfillment-set.ts index 4c74f5548138f..970a032514260 100644 --- a/packages/types/src/fulfillment/mutations/fulfillment-set.ts +++ b/packages/types/src/fulfillment/mutations/fulfillment-set.ts @@ -3,7 +3,7 @@ import { CreateServiceZoneDTO } from "./service-zone" export interface CreateFulfillmentSetDTO { name: string type: string - service_zones: Omit[] + service_zones?: (CreateServiceZoneDTO | { id: string })[] } export interface UpdateFulfillmentSetDTO diff --git a/packages/types/src/fulfillment/mutations/geo-zone.ts b/packages/types/src/fulfillment/mutations/geo-zone.ts index 9ffe5720687a4..e591ed6cfd8db 100644 --- a/packages/types/src/fulfillment/mutations/geo-zone.ts +++ b/packages/types/src/fulfillment/mutations/geo-zone.ts @@ -2,7 +2,6 @@ import { GeoZoneType } from "../common" interface CreateGeoZoneBaseDTO { type: GeoZoneType - service_zone_id: string country_code: string metadata?: Record | null } diff --git a/packages/types/src/fulfillment/mutations/service-zone.ts b/packages/types/src/fulfillment/mutations/service-zone.ts index 0c436e6cfd656..1152bb35ed199 100644 --- a/packages/types/src/fulfillment/mutations/service-zone.ts +++ b/packages/types/src/fulfillment/mutations/service-zone.ts @@ -1,12 +1,8 @@ -import { CreateGeoZoneDTO, UpdateGeoZoneDTO } from "./geo-zone" +import { CreateGeoZoneDTO } from "./geo-zone" export interface CreateServiceZoneDTO { - fulfillment_set_id: string name: string - geo_zones: ( - | Omit - | Omit - )[] + geo_zones?: (CreateGeoZoneDTO | { id: string })[] } export interface UpdateServiceZoneDTO extends Partial { diff --git a/packages/utils/src/common/create-psql-index-helper.ts b/packages/utils/src/common/create-psql-index-helper.ts index a2a030dcbcc8e..7aa35a676d0e7 100644 --- a/packages/utils/src/common/create-psql-index-helper.ts +++ b/packages/utils/src/common/create-psql-index-helper.ts @@ -5,6 +5,7 @@ * @param columns The columns to index * @param type The type of index (e.g GIN, GIST, BTREE, etc) * @param where The where clause + * @param unique If the index should be a unique index * * @example * createPsqlIndexStatementHelper({ @@ -32,16 +33,19 @@ export function createPsqlIndexStatementHelper({ columns, type, where, + unique, }: { name: string tableName: string columns: string | string[] type?: string where?: string + unique?: boolean }) { columns = Array.isArray(columns) ? columns.join(", ") : columns const typeStr = type ? ` USING ${type}` : "" const optionsStr = where ? ` WHERE ${where}` : "" + const uniqueStr = unique ? " UNIQUE" : "" - return `CREATE INDEX IF NOT EXISTS ${name} ON ${tableName}${typeStr} (${columns})${optionsStr}` + return `CREATE${uniqueStr} INDEX IF NOT EXISTS ${name} ON ${tableName}${typeStr} (${columns})${optionsStr}` } From 12994534455824f49c34c1a89a9e245ab8d21632 Mon Sep 17 00:00:00 2001 From: adrien2p Date: Mon, 12 Feb 2024 13:51:53 +0100 Subject: [PATCH 02/29] cleanup --- .../src/repositories/fulfillment-set.ts | 26 +++++-------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/packages/fulfillment/src/repositories/fulfillment-set.ts b/packages/fulfillment/src/repositories/fulfillment-set.ts index b90df6ed0c5e9..f32c544b228bc 100644 --- a/packages/fulfillment/src/repositories/fulfillment-set.ts +++ b/packages/fulfillment/src/repositories/fulfillment-set.ts @@ -1,3 +1,4 @@ +/* import { Context, FulfillmentTypes } from "@medusajs/types" import { DALUtils, promiseAll } from "@medusajs/utils" import { SqlEntityManager } from "@mikro-orm/postgresql" @@ -17,7 +18,7 @@ export class FulfillmentSetRepository extends DALUtils.mikroOrmBaseRepositoryFac ): Promise { const manager = this.getActiveManager(context) - const fulfillmentSets = await promiseAll( + return await promiseAll( data.map(async (fulfillmentSetData) => { const { service_zones, ...fulfillmentSetDataOnly } = fulfillmentSetData const fulfillmentSet = manager.create( @@ -25,33 +26,20 @@ export class FulfillmentSetRepository extends DALUtils.mikroOrmBaseRepositoryFac fulfillmentSetDataOnly ) - // Manager the many to many between the relationship + console.log(JSON.stringify(service_zones, null, 2)) if (service_zones?.length) { - await fulfillmentSet.service_zones.init({ populate: true }) - const fulfillmentSetServiceZones = new Set( - fulfillmentSet.service_zones.getItems().map(({ id }) => id) - ) - - const serviceZoneToAttach = service_zones - .filter(({ id }) => !fulfillmentSetServiceZones.has(id)) - .map(({ id }) => ({ id })) - const serviceZoneToDetach = fulfillmentSet.service_zones - .getItems() - .filter(({ id }) => !service_zones.some((s) => s.id === id)) - + console.log(JSON.stringify(fulfillmentSet.service_zones, null, 2)) fulfillmentSet.service_zones.add( - serviceZoneToAttach.map(({ id }) => - manager.create(ServiceZone, { id } as any) + service_zones.map((serviceZone) => + manager.create(ServiceZone, serviceZone) ) ) - fulfillmentSet.service_zones.remove(serviceZoneToDetach) } manager.persist(fulfillmentSet) return fulfillmentSet }) ) - - return fulfillmentSets } } +*/ From a2ba452deba21ff259641c594b5b9af6a2c35e02 Mon Sep 17 00:00:00 2001 From: adrien2p Date: Mon, 12 Feb 2024 14:19:49 +0100 Subject: [PATCH 03/29] Create geo zones as part of the service zones creation during fulfillment set creation --- .../fulfillment-module-service.spec.ts | 135 +++++++++++++++++- 1 file changed, 132 insertions(+), 3 deletions(-) diff --git a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts index 7ab2fc90c1afb..439238f5a8daf 100644 --- a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts +++ b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts @@ -4,12 +4,11 @@ import { CreateFulfillmentSetDTO, IFulfillmentModuleService, } from "@medusajs/types" -import { SqlEntityManager } from "@mikro-orm/postgresql" import { getInitModuleConfig, MikroOrmWrapper } from "../utils" +import { GeoZoneType } from "@medusajs/utils" describe("fulfillment module service", function () { let service: IFulfillmentModuleService - let testManager: SqlEntityManager let shutdownFunc: () => Promise beforeAll(async () => { @@ -24,7 +23,6 @@ describe("fulfillment module service", function () { beforeEach(async () => { await MikroOrmWrapper.setupDatabase() - testManager = MikroOrmWrapper.forkManager() }) afterEach(async () => { @@ -173,4 +171,135 @@ describe("fulfillment module service", function () { it("should create a collection of fulfillment sets with new service zones and existing service zones", async function () { // TODO }) + + it("should create a new fulfillment set with new service zones and new geo zones", async function () { + const data: CreateFulfillmentSetDTO = { + name: "test", + type: "test-type", + service_zones: [ + { + name: "test", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + ], + }, + ], + } + + const fulfillmentSets = await service.create(data) + + expect(fulfillmentSets).toHaveLength(1) + expect(fulfillmentSets[0]).toEqual( + expect.objectContaining({ + id: expect.any(String), + name: data.name, + type: data.type, + service_zones: expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + name: (data.service_zones![0] as any).name, + geo_zones: expect.arrayContaining([ + expect.objectContaining({ + type: (data.service_zones![0] as any).geo_zones[0].type, + country_code: (data.service_zones![0] as any).geo_zones[0] + .country_code, + }), + ]), + }), + ]), + }) + ) + }) + + it("should create a collection of fulfillment sets with new service zones and new geo zones", async function () { + const data: CreateFulfillmentSetDTO[] = [ + { + name: "test", + type: "test-type", + service_zones: [ + { + name: "test", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + ], + }, + ], + }, + { + name: "test2", + type: "test-type2", + service_zones: [ + { + name: "test", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + ], + }, + ], + }, + { + name: "test3", + type: "test-type3", + service_zones: [ + { + name: "test2", + geo_zones: [ + { + type: GeoZoneType.CITY, + country_code: "fr", + city: "lyon", + }, + ], + }, + ], + }, + ] + + const fulfillmentSets = await service.create(data) + + expect(fulfillmentSets).toHaveLength(3) + + let i = 0 + for (const data_ of data) { + expect(fulfillmentSets[i]).toEqual( + expect.objectContaining({ + id: expect.any(String), + name: data_.name, + type: data_.type, + service_zones: expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + name: (data_.service_zones![0] as any).name, + geo_zones: expect.arrayContaining([ + expect.objectContaining({ + type: (data_.service_zones![0] as any).geo_zones[0].type, + country_code: (data_.service_zones![0] as any).geo_zones[0] + .country_code, + }), + ]), + }), + ]), + }) + ) + ++i + } + + // The two first fulfillment sets share the same service zone + expect(fulfillmentSets[0].service_zones[0].geo_zones[0].id).toEqual( + fulfillmentSets[1].service_zones[0].geo_zones[0].id + ) + }) + + it("should create a collection of fulfillment sets with new service zones and existing service zones", async function () { + // TODO + }) }) From e42052bd4395822d8a97381e3e04d6f2bd6db73a Mon Sep 17 00:00:00 2001 From: adrien2p Date: Mon, 12 Feb 2024 14:20:31 +0100 Subject: [PATCH 04/29] cleanup --- .../__tests__/fulfillment-module-service.spec.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts index 439238f5a8daf..9736fd64ad5de 100644 --- a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts +++ b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts @@ -168,10 +168,6 @@ describe("fulfillment module service", function () { ) }) - it("should create a collection of fulfillment sets with new service zones and existing service zones", async function () { - // TODO - }) - it("should create a new fulfillment set with new service zones and new geo zones", async function () { const data: CreateFulfillmentSetDTO = { name: "test", @@ -298,8 +294,4 @@ describe("fulfillment module service", function () { fulfillmentSets[1].service_zones[0].geo_zones[0].id ) }) - - it("should create a collection of fulfillment sets with new service zones and existing service zones", async function () { - // TODO - }) }) From baf85d8b368f0e4c58ec10b6ba61f317e7d24853 Mon Sep 17 00:00:00 2001 From: adrien2p Date: Mon, 12 Feb 2024 14:23:41 +0100 Subject: [PATCH 05/29] wrap in a separate describe --- .../fulfillment-module-service.spec.ts | 362 +++++++++--------- 1 file changed, 182 insertions(+), 180 deletions(-) diff --git a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts index 9736fd64ad5de..7b580256d94f8 100644 --- a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts +++ b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts @@ -33,85 +33,56 @@ describe("fulfillment module service", function () { await shutdownFunc() }) - it("should create a new fulfillment set", async function () { - const data: CreateFulfillmentSetDTO = { - name: "test", - type: "test-type", - } - - const fulfillmentSets = await service.create(data) - - expect(fulfillmentSets).toHaveLength(1) - expect(fulfillmentSets[0]).toEqual( - expect.objectContaining({ - id: expect.any(String), - name: data.name, - type: data.type, - }) - ) - }) - - it("should create a collection of fulfillment sets", async function () { - const data = [ - { + describe("on create", () => { + it("should create a new fulfillment set", async function () { + const data: CreateFulfillmentSetDTO = { name: "test", type: "test-type", - }, - { - name: "test2", - type: "test-type2", - }, - ] - - const fulfillmentSets = await service.create(data) + } - expect(fulfillmentSets).toHaveLength(2) + const fulfillmentSets = await service.create(data) - let i = 0 - for (const data_ of data) { - expect(fulfillmentSets[i]).toEqual( + expect(fulfillmentSets).toHaveLength(1) + expect(fulfillmentSets[0]).toEqual( expect.objectContaining({ id: expect.any(String), - name: data_.name, - type: data_.type, + name: data.name, + type: data.type, }) ) - ++i - } - }) + }) - it("should create a new fulfillment set with new service zones", async function () { - const data = { - name: "test", - type: "test-type", - service_zones: [ + it("should create a collection of fulfillment sets", async function () { + const data = [ { name: "test", + type: "test-type", }, - ], - } + { + name: "test2", + type: "test-type2", + }, + ] + + const fulfillmentSets = await service.create(data) - const fulfillmentSets = await service.create(data) + expect(fulfillmentSets).toHaveLength(2) - expect(fulfillmentSets).toHaveLength(1) - expect(fulfillmentSets[0]).toEqual( - expect.objectContaining({ - id: expect.any(String), - name: data.name, - type: data.type, - service_zones: expect.arrayContaining([ + let i = 0 + for (const data_ of data) { + expect(fulfillmentSets[i]).toEqual( expect.objectContaining({ id: expect.any(String), - name: data.service_zones[0].name, - }), - ]), - }) - ) - }) + name: data_.name, + type: data_.type, + }) + ) + ++i + } + }) - it("should create a collection of fulfillment sets with new service zones", async function () { - const data = [ - { + it("should create a new fulfillment set with new service zones", async function () { + const data = { name: "test", type: "test-type", service_zones: [ @@ -119,100 +90,87 @@ describe("fulfillment module service", function () { name: "test", }, ], - }, - { - name: "test2", - type: "test-type2", - service_zones: [ - { - name: "test", - }, - ], - }, - { - name: "test3", - type: "test-type3", - service_zones: [ - { - name: "test2", - }, - ], - }, - ] + } - const fulfillmentSets = await service.create(data) + const fulfillmentSets = await service.create(data) - expect(fulfillmentSets).toHaveLength(3) - - let i = 0 - for (const data_ of data) { - expect(fulfillmentSets[i]).toEqual( + expect(fulfillmentSets).toHaveLength(1) + expect(fulfillmentSets[0]).toEqual( expect.objectContaining({ id: expect.any(String), - name: data_.name, - type: data_.type, + name: data.name, + type: data.type, service_zones: expect.arrayContaining([ expect.objectContaining({ id: expect.any(String), - name: data_.service_zones[0].name, + name: data.service_zones[0].name, }), ]), }) ) - ++i - } - - // The two first fulfillment sets share the same service zone - expect(fulfillmentSets[0].service_zones[0].id).toEqual( - fulfillmentSets[1].service_zones[0].id - ) - }) + }) - it("should create a new fulfillment set with new service zones and new geo zones", async function () { - const data: CreateFulfillmentSetDTO = { - name: "test", - type: "test-type", - service_zones: [ + it("should create a collection of fulfillment sets with new service zones", async function () { + const data = [ { name: "test", - geo_zones: [ + type: "test-type", + service_zones: [ + { + name: "test", + }, + ], + }, + { + name: "test2", + type: "test-type2", + service_zones: [ + { + name: "test", + }, + ], + }, + { + name: "test3", + type: "test-type3", + service_zones: [ { - type: GeoZoneType.COUNTRY, - country_code: "fr", + name: "test2", }, ], }, - ], - } + ] - const fulfillmentSets = await service.create(data) + const fulfillmentSets = await service.create(data) - expect(fulfillmentSets).toHaveLength(1) - expect(fulfillmentSets[0]).toEqual( - expect.objectContaining({ - id: expect.any(String), - name: data.name, - type: data.type, - service_zones: expect.arrayContaining([ + expect(fulfillmentSets).toHaveLength(3) + + let i = 0 + for (const data_ of data) { + expect(fulfillmentSets[i]).toEqual( expect.objectContaining({ id: expect.any(String), - name: (data.service_zones![0] as any).name, - geo_zones: expect.arrayContaining([ + name: data_.name, + type: data_.type, + service_zones: expect.arrayContaining([ expect.objectContaining({ - type: (data.service_zones![0] as any).geo_zones[0].type, - country_code: (data.service_zones![0] as any).geo_zones[0] - .country_code, + id: expect.any(String), + name: data_.service_zones[0].name, }), ]), - }), - ]), - }) - ) - }) + }) + ) + ++i + } - it("should create a collection of fulfillment sets with new service zones and new geo zones", async function () { - const data: CreateFulfillmentSetDTO[] = [ - { + // The two first fulfillment sets share the same service zone + expect(fulfillmentSets[0].service_zones[0].id).toEqual( + fulfillmentSets[1].service_zones[0].id + ) + }) + + it("should create a new fulfillment set with new service zones and new geo zones", async function () { + const data: CreateFulfillmentSetDTO = { name: "test", type: "test-type", service_zones: [ @@ -226,59 +184,24 @@ describe("fulfillment module service", function () { ], }, ], - }, - { - name: "test2", - type: "test-type2", - service_zones: [ - { - name: "test", - geo_zones: [ - { - type: GeoZoneType.COUNTRY, - country_code: "fr", - }, - ], - }, - ], - }, - { - name: "test3", - type: "test-type3", - service_zones: [ - { - name: "test2", - geo_zones: [ - { - type: GeoZoneType.CITY, - country_code: "fr", - city: "lyon", - }, - ], - }, - ], - }, - ] + } - const fulfillmentSets = await service.create(data) + const fulfillmentSets = await service.create(data) - expect(fulfillmentSets).toHaveLength(3) - - let i = 0 - for (const data_ of data) { - expect(fulfillmentSets[i]).toEqual( + expect(fulfillmentSets).toHaveLength(1) + expect(fulfillmentSets[0]).toEqual( expect.objectContaining({ id: expect.any(String), - name: data_.name, - type: data_.type, + name: data.name, + type: data.type, service_zones: expect.arrayContaining([ expect.objectContaining({ id: expect.any(String), - name: (data_.service_zones![0] as any).name, + name: (data.service_zones![0] as any).name, geo_zones: expect.arrayContaining([ expect.objectContaining({ - type: (data_.service_zones![0] as any).geo_zones[0].type, - country_code: (data_.service_zones![0] as any).geo_zones[0] + type: (data.service_zones![0] as any).geo_zones[0].type, + country_code: (data.service_zones![0] as any).geo_zones[0] .country_code, }), ]), @@ -286,12 +209,91 @@ describe("fulfillment module service", function () { ]), }) ) - ++i - } + }) - // The two first fulfillment sets share the same service zone - expect(fulfillmentSets[0].service_zones[0].geo_zones[0].id).toEqual( - fulfillmentSets[1].service_zones[0].geo_zones[0].id - ) + it("should create a collection of fulfillment sets with new service zones and new geo zones", async function () { + const data: CreateFulfillmentSetDTO[] = [ + { + name: "test", + type: "test-type", + service_zones: [ + { + name: "test", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + ], + }, + ], + }, + { + name: "test2", + type: "test-type2", + service_zones: [ + { + name: "test", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + ], + }, + ], + }, + { + name: "test3", + type: "test-type3", + service_zones: [ + { + name: "test2", + geo_zones: [ + { + type: GeoZoneType.CITY, + country_code: "fr", + city: "lyon", + }, + ], + }, + ], + }, + ] + + const fulfillmentSets = await service.create(data) + + expect(fulfillmentSets).toHaveLength(3) + + let i = 0 + for (const data_ of data) { + expect(fulfillmentSets[i]).toEqual( + expect.objectContaining({ + id: expect.any(String), + name: data_.name, + type: data_.type, + service_zones: expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + name: (data_.service_zones![0] as any).name, + geo_zones: expect.arrayContaining([ + expect.objectContaining({ + type: (data_.service_zones![0] as any).geo_zones[0].type, + country_code: (data_.service_zones![0] as any).geo_zones[0] + .country_code, + }), + ]), + }), + ]), + }) + ) + ++i + } + + // The two first fulfillment sets share the same geo zone for their service zones + expect(fulfillmentSets[0].service_zones[0].geo_zones[0].id).toEqual( + fulfillmentSets[1].service_zones[0].geo_zones[0].id + ) + }) }) }) From 1fa628ce2b3751127def41282204d4b2c935c8ab Mon Sep 17 00:00:00 2001 From: adrien2p Date: Mon, 12 Feb 2024 14:24:15 +0100 Subject: [PATCH 06/29] rm todo --- packages/fulfillment/src/services/fulfillment-module-service.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/fulfillment/src/services/fulfillment-module-service.ts b/packages/fulfillment/src/services/fulfillment-module-service.ts index 096f5ddd7778a..34afa3f51a4eb 100644 --- a/packages/fulfillment/src/services/fulfillment-module-service.ts +++ b/packages/fulfillment/src/services/fulfillment-module-service.ts @@ -137,8 +137,6 @@ export default class FulfillmentModuleService< * to create. */ - // TODO manage the geo zones as well - if (service_zones?.length) { const serviceZoneTuple: [ string, From dc2d2b57495d84e069cb2ba4b51490ec9024052a Mon Sep 17 00:00:00 2001 From: adrien2p Date: Mon, 12 Feb 2024 15:41:54 +0100 Subject: [PATCH 07/29] trully run db migrations --- .../src/migrations/Migration20240124154000.ts | 2 +- .../fulfillment-module-service.spec.ts | 15 + .../integration-tests/utils/database.ts | 9 +- packages/fulfillment/jest.config.js | 1 + .../.snapshot-medusa-fulfillment.json | 1888 +++++++++++++++++ .../src/migrations/Migration20240212133507.ts | 207 ++ packages/medusa-test-utils/src/database.ts | 72 +- .../src/common/create-psql-index-helper.ts | 7 +- 8 files changed, 2169 insertions(+), 32 deletions(-) create mode 100644 packages/fulfillment/src/migrations/.snapshot-medusa-fulfillment.json create mode 100644 packages/fulfillment/src/migrations/Migration20240212133507.ts diff --git a/packages/customer/src/migrations/Migration20240124154000.ts b/packages/customer/src/migrations/Migration20240124154000.ts index c44f78d7f29a0..93c61f3d72dec 100644 --- a/packages/customer/src/migrations/Migration20240124154000.ts +++ b/packages/customer/src/migrations/Migration20240124154000.ts @@ -23,7 +23,7 @@ export class Migration20240124154000 extends Migration { 'create index if not exists "IDX_customer_address_customer_id" on "customer_address" ("customer_id");' ) this.addSql( - 'create unique index "IDX_customer_address_unqiue_customer_billing" on "customer_address" ("customer_id") where "is_default_billing" = true;' + 'create unique index "IDX_customer_address_unique_customer_billing" on "customer_address" ("customer_id") where "is_default_billing" = true;' ) this.addSql( 'create unique index "IDX_customer_address_unique_customer_shipping" on "customer_address" ("customer_id") where "is_default_shipping" = true;' diff --git a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts index 7b580256d94f8..d17463bcc07ee 100644 --- a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts +++ b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts @@ -295,5 +295,20 @@ describe("fulfillment module service", function () { fulfillmentSets[1].service_zones[0].geo_zones[0].id ) }) + + describe("should fail", () => { + it(`on duplicated fulfillment set name`, async function () { + const data: CreateFulfillmentSetDTO = { + name: "test", + type: "test-type", + } + + await service.create(data) + await service.create(data) + + const ful = await service.list({}) + console.log(JSON.stringify(ful, null, 2)) + }) + }) }) }) diff --git a/packages/fulfillment/integration-tests/utils/database.ts b/packages/fulfillment/integration-tests/utils/database.ts index 118464d4d76a0..d1ba6a830cef1 100644 --- a/packages/fulfillment/integration-tests/utils/database.ts +++ b/packages/fulfillment/integration-tests/utils/database.ts @@ -2,17 +2,12 @@ import { TestDatabaseUtils } from "medusa-test-utils" import * as Models from "@models" -const pathToMigrations = "../../src/migrations" const mikroOrmEntities = Models as unknown as any[] export const MikroOrmWrapper = TestDatabaseUtils.getMikroOrmWrapper( mikroOrmEntities, - pathToMigrations -) - -export const MikroOrmConfig = TestDatabaseUtils.getMikroOrmConfig( - mikroOrmEntities, - pathToMigrations + null, + process.env.MEDUSA_FULFILLMENT_DB_SCHEMA ) export const DB_URL = TestDatabaseUtils.getDatabaseURL() diff --git a/packages/fulfillment/jest.config.js b/packages/fulfillment/jest.config.js index 456054fe8ae27..4f5c805538355 100644 --- a/packages/fulfillment/jest.config.js +++ b/packages/fulfillment/jest.config.js @@ -4,6 +4,7 @@ module.exports = { "^@services": "/src/services", "^@repositories": "/src/repositories", "^@types": "/src/types", + "^@migrations": "/src/migrations", }, transform: { "^.+\\.[jt]s?$": [ diff --git a/packages/fulfillment/src/migrations/.snapshot-medusa-fulfillment.json b/packages/fulfillment/src/migrations/.snapshot-medusa-fulfillment.json new file mode 100644 index 0000000000000..6020fa02dc4da --- /dev/null +++ b/packages/fulfillment/src/migrations/.snapshot-medusa-fulfillment.json @@ -0,0 +1,1888 @@ +{ + "namespaces": [ + "public" + ], + "name": "public", + "tables": [ + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "fulfillment_id": { + "name": "fulfillment_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "company": { + "name": "company", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "first_name": { + "name": "first_name", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "last_name": { + "name": "last_name", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "address_1": { + "name": "address_1", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "address_2": { + "name": "address_2", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "city": { + "name": "city", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "country_code": { + "name": "country_code", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "province": { + "name": "province", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "postal_code": { + "name": "postal_code", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "phone": { + "name": "phone", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "fulfillment_address", + "schema": "public", + "indexes": [ + { + "keyName": "IDX_fulfillment_address_fulfillment_id", + "columnNames": [ + "fulfillment_id" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS IDX_fulfillment_address_fulfillment_id ON fulfillment_address (fulfillment_id) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_fulfillment_address_deleted_at", + "columnNames": [ + "deleted_at" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS IDX_fulfillment_address_deleted_at ON fulfillment_address (deleted_at) WHERE deleted_at IS NOT NULL" + }, + { + "keyName": "fulfillment_address_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": {} + }, + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "name": { + "name": "name", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "type": { + "name": "type", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "fulfillment_set", + "schema": "public", + "indexes": [ + { + "keyName": "IDX_fulfillment_set_name_unique", + "columnNames": [ + "name" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE UNIQUE INDEX IF NOT EXISTS IDX_fulfillment_set_name_unique ON fulfillment_set (name) WHERE deleted_at IS NOT NULL" + }, + { + "keyName": "IDX_fulfillment_set_deleted_at", + "columnNames": [ + "deleted_at" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS IDX_fulfillment_set_deleted_at ON fulfillment_set (deleted_at) WHERE deleted_at IS NOT NULL" + }, + { + "keyName": "fulfillment_set_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": {} + }, + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "type": { + "name": "type", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "'country'", + "enumItems": [ + "country", + "province", + "city", + "zip" + ], + "mappedType": "enum" + }, + "country_code": { + "name": "country_code", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "province_code": { + "name": "province_code", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "city": { + "name": "city", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "postal_expression": { + "name": "postal_expression", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "geo_zone", + "schema": "public", + "indexes": [ + { + "keyName": "IDX_geo_zone_country_code", + "columnNames": [ + "country_code" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS IDX_geo_zone_country_code ON geo_zone (country_code) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_geo_zone_province_code", + "columnNames": [ + "province_code" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "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" + }, + { + "keyName": "IDX_geo_zone_city", + "columnNames": [ + "city" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS IDX_geo_zone_city ON geo_zone (city) WHERE deleted_at IS NULL AND city IS NOT NULL" + }, + { + "keyName": "IDX_geo_zone_deleted_at", + "columnNames": [ + "deleted_at" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS IDX_geo_zone_deleted_at ON geo_zone (deleted_at) WHERE deleted_at IS NOT NULL" + }, + { + "keyName": "geo_zone_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": {} + }, + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "service_provider", + "schema": "public", + "indexes": [ + { + "keyName": "IDX_service_provider_deleted_at", + "columnNames": [ + "deleted_at" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS IDX_service_provider_deleted_at ON service_provider (deleted_at) WHERE deleted_at IS NOT NULL" + }, + { + "keyName": "service_provider_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": {} + }, + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "name": { + "name": "name", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "service_zone", + "schema": "public", + "indexes": [ + { + "keyName": "IDX_service_zone_name_unique", + "columnNames": [ + "name" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE UNIQUE INDEX IF NOT EXISTS IDX_service_zone_name_unique ON service_zone (name) WHERE deleted_at IS NOT NULL" + }, + { + "keyName": "IDX_service_zone_deleted_at", + "columnNames": [ + "deleted_at" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS IDX_service_zone_deleted_at ON service_zone (deleted_at) WHERE deleted_at IS NOT NULL" + }, + { + "keyName": "service_zone_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": {} + }, + { + "columns": { + "service_zone_id": { + "name": "service_zone_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "geo_zone_id": { + "name": "geo_zone_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + } + }, + "name": "service_zone_geo_zones", + "schema": "public", + "indexes": [ + { + "keyName": "service_zone_geo_zones_pkey", + "columnNames": [ + "service_zone_id", + "geo_zone_id" + ], + "composite": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "service_zone_geo_zones_service_zone_id_foreign": { + "constraintName": "service_zone_geo_zones_service_zone_id_foreign", + "columnNames": [ + "service_zone_id" + ], + "localTableName": "public.service_zone_geo_zones", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.service_zone", + "deleteRule": "cascade", + "updateRule": "cascade" + }, + "service_zone_geo_zones_geo_zone_id_foreign": { + "constraintName": "service_zone_geo_zones_geo_zone_id_foreign", + "columnNames": [ + "geo_zone_id" + ], + "localTableName": "public.service_zone_geo_zones", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.geo_zone", + "deleteRule": "cascade", + "updateRule": "cascade" + } + } + }, + { + "columns": { + "fulfillment_set_id": { + "name": "fulfillment_set_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "service_zone_id": { + "name": "service_zone_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + } + }, + "name": "fulfillment_set_service_zones", + "schema": "public", + "indexes": [ + { + "keyName": "fulfillment_set_service_zones_pkey", + "columnNames": [ + "fulfillment_set_id", + "service_zone_id" + ], + "composite": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "fulfillment_set_service_zones_fulfillment_set_id_foreign": { + "constraintName": "fulfillment_set_service_zones_fulfillment_set_id_foreign", + "columnNames": [ + "fulfillment_set_id" + ], + "localTableName": "public.fulfillment_set_service_zones", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.fulfillment_set", + "deleteRule": "cascade", + "updateRule": "cascade" + }, + "fulfillment_set_service_zones_service_zone_id_foreign": { + "constraintName": "fulfillment_set_service_zones_service_zone_id_foreign", + "columnNames": [ + "service_zone_id" + ], + "localTableName": "public.fulfillment_set_service_zones", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.service_zone", + "deleteRule": "cascade", + "updateRule": "cascade" + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "label": { + "name": "label", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "description": { + "name": "description", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "code": { + "name": "code", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "shipping_option_id": { + "name": "shipping_option_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "shipping_option_type", + "schema": "public", + "indexes": [ + { + "keyName": "IDX_shipping_option_type_shipping_option_id", + "columnNames": [ + "shipping_option_id" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS IDX_shipping_option_type_shipping_option_id ON shipping_option_type (shipping_option_id) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_shipping_option_type_deleted_at", + "columnNames": [ + "deleted_at" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS IDX_shipping_option_type_deleted_at ON shipping_option_type (deleted_at) WHERE deleted_at IS NOT NULL" + }, + { + "keyName": "shipping_option_type_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": {} + }, + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "shipping_profile", + "schema": "public", + "indexes": [ + { + "keyName": "IDX_shipping_profile_deleted_at", + "columnNames": [ + "deleted_at" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS IDX_shipping_profile_deleted_at ON shipping_profile (deleted_at) WHERE deleted_at IS NOT NULL" + }, + { + "keyName": "shipping_profile_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": {} + }, + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "name": { + "name": "name", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "price_type": { + "name": "price_type", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "'calculated'", + "enumItems": [ + "calculated", + "flat" + ], + "mappedType": "enum" + }, + "service_zone_id": { + "name": "service_zone_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "shipping_profile_id": { + "name": "shipping_profile_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "service_provider_id": { + "name": "service_provider_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "shipping_option_type_id": { + "name": "shipping_option_type_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "data": { + "name": "data", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "shipping_option", + "schema": "public", + "indexes": [ + { + "columnNames": [ + "shipping_option_type_id" + ], + "composite": false, + "keyName": "shipping_option_shipping_option_type_id_unique", + "primary": false, + "unique": true + }, + { + "keyName": "IDX_shipping_option_service_zone_id", + "columnNames": [ + "service_zone_id" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS IDX_shipping_option_service_zone_id ON shipping_option (service_zone_id) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_shipping_option_shipping_profile_id", + "columnNames": [ + "shipping_profile_id" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS IDX_shipping_option_shipping_profile_id ON shipping_option (shipping_profile_id) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_shipping_option_service_provider_id", + "columnNames": [ + "service_provider_id" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS IDX_shipping_option_service_provider_id ON shipping_option (service_provider_id) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_shipping_option_shipping_option_type_id", + "columnNames": [ + "shipping_option_type_id" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS IDX_shipping_option_shipping_option_type_id ON shipping_option (shipping_option_type_id) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_shipping_option_deleted_at", + "columnNames": [ + "deleted_at" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS IDX_shipping_option_deleted_at ON shipping_option (deleted_at) WHERE deleted_at IS NOT NULL" + }, + { + "keyName": "shipping_option_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "shipping_option_service_zone_id_foreign": { + "constraintName": "shipping_option_service_zone_id_foreign", + "columnNames": [ + "service_zone_id" + ], + "localTableName": "public.shipping_option", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.service_zone", + "updateRule": "cascade" + }, + "shipping_option_shipping_profile_id_foreign": { + "constraintName": "shipping_option_shipping_profile_id_foreign", + "columnNames": [ + "shipping_profile_id" + ], + "localTableName": "public.shipping_option", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.shipping_profile", + "updateRule": "cascade" + }, + "shipping_option_service_provider_id_foreign": { + "constraintName": "shipping_option_service_provider_id_foreign", + "columnNames": [ + "service_provider_id" + ], + "localTableName": "public.shipping_option", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.service_provider", + "updateRule": "cascade" + }, + "shipping_option_shipping_option_type_id_foreign": { + "constraintName": "shipping_option_shipping_option_type_id_foreign", + "columnNames": [ + "shipping_option_type_id" + ], + "localTableName": "public.shipping_option", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.shipping_option_type", + "updateRule": "cascade" + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "attribute": { + "name": "attribute", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "operator": { + "name": "operator", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "value": { + "name": "value", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "shipping_option_id": { + "name": "shipping_option_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "shipping_option_rule", + "schema": "public", + "indexes": [ + { + "keyName": "IDX_shipping_option_rule_deleted_at", + "columnNames": [ + "deleted_at" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS IDX_shipping_option_rule_deleted_at ON shipping_option_rule (deleted_at) WHERE deleted_at IS NOT NULL" + }, + { + "keyName": "shipping_option_rule_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "shipping_option_rule_shipping_option_id_foreign": { + "constraintName": "shipping_option_rule_shipping_option_id_foreign", + "columnNames": [ + "shipping_option_id" + ], + "localTableName": "public.shipping_option_rule", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.shipping_option", + "updateRule": "cascade" + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "location_id": { + "name": "location_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "packed_at": { + "name": "packed_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + }, + "shipped_at": { + "name": "shipped_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + }, + "canceled_at": { + "name": "canceled_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + }, + "data": { + "name": "data", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "shipping_option_id": { + "name": "shipping_option_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "delivery_address_id": { + "name": "delivery_address_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "items_id": { + "name": "items_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "fulfillment", + "schema": "public", + "indexes": [ + { + "keyName": "IDX_fulfillment_location_id", + "columnNames": [ + "location_id" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS IDX_fulfillment_location_id ON fulfillment (location_id) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_fulfillment_provider_id", + "columnNames": [ + "provider_id" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS IDX_fulfillment_provider_id ON fulfillment (provider_id) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_fulfillment_shipping_option_id", + "columnNames": [ + "shipping_option_id" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS IDX_fulfillment_shipping_option_id ON fulfillment (shipping_option_id) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_fulfillment_deleted_at", + "columnNames": [ + "deleted_at" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS IDX_fulfillment_deleted_at ON fulfillment (deleted_at) WHERE deleted_at IS NOT NULL" + }, + { + "keyName": "fulfillment_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "fulfillment_shipping_option_id_foreign": { + "constraintName": "fulfillment_shipping_option_id_foreign", + "columnNames": [ + "shipping_option_id" + ], + "localTableName": "public.fulfillment", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.shipping_option", + "deleteRule": "set null", + "updateRule": "cascade" + }, + "fulfillment_provider_id_foreign": { + "constraintName": "fulfillment_provider_id_foreign", + "columnNames": [ + "provider_id" + ], + "localTableName": "public.fulfillment", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.service_provider", + "updateRule": "cascade" + }, + "fulfillment_delivery_address_id_foreign": { + "constraintName": "fulfillment_delivery_address_id_foreign", + "columnNames": [ + "delivery_address_id" + ], + "localTableName": "public.fulfillment", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.fulfillment_address", + "updateRule": "cascade" + }, + "fulfillment_items_id_foreign": { + "constraintName": "fulfillment_items_id_foreign", + "columnNames": [ + "items_id" + ], + "localTableName": "public.fulfillment", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.fulfillment_item", + "updateRule": "cascade" + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "tracking_number": { + "name": "tracking_number", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "tracking_url": { + "name": "tracking_url", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "label_url": { + "name": "label_url", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "fulfillment_id": { + "name": "fulfillment_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "fulfillment_label", + "schema": "public", + "indexes": [ + { + "keyName": "IDX_fulfillment_label_fulfillment_id", + "columnNames": [ + "fulfillment_id" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS IDX_fulfillment_label_fulfillment_id ON fulfillment_label (fulfillment_id) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_fulfillment_label_deleted_at", + "columnNames": [ + "deleted_at" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS IDX_fulfillment_label_deleted_at ON fulfillment_label (deleted_at) WHERE deleted_at IS NOT NULL" + }, + { + "keyName": "fulfillment_label_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "fulfillment_label_fulfillment_id_foreign": { + "constraintName": "fulfillment_label_fulfillment_id_foreign", + "columnNames": [ + "fulfillment_id" + ], + "localTableName": "public.fulfillment_label", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.fulfillment", + "updateRule": "cascade" + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "title": { + "name": "title", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "sku": { + "name": "sku", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "barcode": { + "name": "barcode", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "quantity": { + "name": "quantity", + "type": "numeric", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "decimal" + }, + "line_item_id": { + "name": "line_item_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "inventory_item_id": { + "name": "inventory_item_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "fulfillment_id": { + "name": "fulfillment_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "fulfillment_item", + "schema": "public", + "indexes": [ + { + "keyName": "IDX_fulfillment_item_line_item_id", + "columnNames": [ + "line_item_id" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS IDX_fulfillment_item_fulfillment_id ON fulfillment_item (line_item_id) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_fulfillment_item_inventory_item_id", + "columnNames": [ + "inventory_item_id" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS IDX_fulfillment_item_fulfillment_id ON fulfillment_item (inventory_item_id) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_fulfillment_item_fulfillment_id", + "columnNames": [ + "fulfillment_id" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS IDX_fulfillment_item_fulfillment_id ON fulfillment_item (fulfillment_id) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_fulfillment_item_deleted_at", + "columnNames": [ + "deleted_at" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS IDX_fulfillment_item_deleted_at ON fulfillment_item (deleted_at) WHERE deleted_at IS NOT NULL" + }, + { + "keyName": "fulfillment_item_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "fulfillment_item_fulfillment_id_foreign": { + "constraintName": "fulfillment_item_fulfillment_id_foreign", + "columnNames": [ + "fulfillment_id" + ], + "localTableName": "public.fulfillment_item", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.fulfillment", + "updateRule": "cascade" + } + } + } + ] +} diff --git a/packages/fulfillment/src/migrations/Migration20240212133507.ts b/packages/fulfillment/src/migrations/Migration20240212133507.ts new file mode 100644 index 0000000000000..6a9abd4efe737 --- /dev/null +++ b/packages/fulfillment/src/migrations/Migration20240212133507.ts @@ -0,0 +1,207 @@ +import { Migration } from "@mikro-orm/migrations" + +export class Migration20240212133507 extends Migration { + async up(): Promise { + this.addSql( + 'create table if not exists "fulfillment_address" ("id" text not null, "fulfillment_id" text 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_fulfillment_id ON fulfillment_address (fulfillment_id) WHERE deleted_at IS NULL;" + ) + 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_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 IDX_fulfillment_set_name_unique ON fulfillment_set (name) WHERE deleted_at IS NOT NULL;" + "alter table fulfillment_set add constraint IDX_fulfillment_set_name_unique UNIQUE (name)" + ) + 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 "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, "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_deleted_at ON geo_zone (deleted_at) WHERE deleted_at IS NOT NULL;" + ) + + this.addSql( + 'create table if not exists "service_provider" ("id" 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 "service_provider_pkey" primary key ("id"));' + ) + this.addSql( + "CREATE INDEX IF NOT EXISTS IDX_service_provider_deleted_at ON service_provider (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, "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 NOT 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 "service_zone_geo_zones" ("service_zone_id" text not null, "geo_zone_id" text not null, constraint "service_zone_geo_zones_pkey" primary key ("service_zone_id", "geo_zone_id"));' + ) + + this.addSql( + 'create table if not exists "fulfillment_set_service_zones" ("fulfillment_set_id" text not null, "service_zone_id" text not null, constraint "fulfillment_set_service_zones_pkey" primary key ("fulfillment_set_id", "service_zone_id"));' + ) + + 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, "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_type_pkey" primary key ("id"));' + ) + this.addSql( + "CREATE INDEX IF NOT EXISTS IDX_shipping_option_type_shipping_option_id ON shipping_option_type (shipping_option_id) WHERE deleted_at IS NULL;" + ) + 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, "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 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 not null, "service_provider_id" text not null, "shipping_option_type_id" text null, "data" jsonb null, "metadata" jsonb 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_service_provider_id ON shipping_option (service_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 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_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 not null, "shipping_option_id" text null, "metadata" jsonb null, "delivery_address_id" text not null, "items_id" text not 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( + "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, "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_fulfillment_id ON fulfillment_item (line_item_id) WHERE deleted_at IS NULL;" + ) + this.addSql( + "CREATE INDEX IF NOT EXISTS IDX_fulfillment_item_fulfillment_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_geo_zones" add constraint "service_zone_geo_zones_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 "service_zone_geo_zones" add constraint "service_zone_geo_zones_geo_zone_id_foreign" foreign key ("geo_zone_id") references "geo_zone" ("id") on update cascade on delete cascade;' + ) + + this.addSql( + 'alter table if exists "fulfillment_set_service_zones" add constraint "fulfillment_set_service_zones_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 "fulfillment_set_service_zones" add constraint "fulfillment_set_service_zones_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;' + ) + 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;' + ) + this.addSql( + 'alter table if exists "shipping_option" add constraint "shipping_option_service_provider_id_foreign" foreign key ("service_provider_id") references "service_provider" ("id") on update cascade;' + ) + 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;' + ) + + 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;' + ) + + 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_provider_id_foreign" foreign key ("provider_id") references "service_provider" ("id") on update cascade;' + ) + 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;' + ) + this.addSql( + 'alter table if exists "fulfillment" add constraint "fulfillment_items_id_foreign" foreign key ("items_id") references "fulfillment_item" ("id") on update 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;' + ) + + 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;' + ) + } +} diff --git a/packages/medusa-test-utils/src/database.ts b/packages/medusa-test-utils/src/database.ts index 3f694a1acaa60..30b513ef7a365 100644 --- a/packages/medusa-test-utils/src/database.ts +++ b/packages/medusa-test-utils/src/database.ts @@ -1,6 +1,6 @@ -import { TSMigrationGenerator } from "@mikro-orm/migrations" import { MikroORM, Options, SqlEntityManager } from "@mikro-orm/postgresql" import * as process from "process" +import { Migrator } from "@mikro-orm/migrations" export function getDatabaseURL(): string { const DB_HOST = process.env.DB_HOST ?? "localhost" @@ -15,7 +15,8 @@ export function getDatabaseURL(): string { export function getMikroOrmConfig( mikroOrmEntities: any[], - pathToMigrations: string + pathToMigrations: string, // deprecated, auto inferred + schema?: string ): Options { const DB_URL = getDatabaseURL() @@ -23,23 +24,17 @@ export function getMikroOrmConfig( type: "postgresql", clientUrl: DB_URL, entities: Object.values(mikroOrmEntities), - schema: process.env.MEDUSA_DB_SCHEMA, + schema: schema ?? process.env.MEDUSA_DB_SCHEMA, debug: false, - migrations: { - path: pathToMigrations, - pathTs: pathToMigrations, - glob: "!(*.d).{js,ts}", - silent: true, - dropTables: true, - transactional: true, - allOrNothing: true, - safe: false, - generator: TSMigrationGenerator, - }, + extensions: [Migrator], } } export interface TestDatabase { + mikroOrmEntities: any[] + pathToMigrations: any // deprecated, auto inferred + schema?: string + orm: MikroORM | null manager: SqlEntityManager | null @@ -52,9 +47,14 @@ export interface TestDatabase { export function getMikroOrmWrapper( mikroOrmEntities: any[], - pathToMigrations: string + pathToMigrations: string, // deprecated, auto inferred + schema?: string ): TestDatabase { return { + mikroOrmEntities, + pathToMigrations, // deprecated, auto inferred + schema: schema ?? process.env.MEDUSA_DB_SCHEMA, + orm: null, manager: null, @@ -83,18 +83,36 @@ export function getMikroOrmWrapper( }, async setupDatabase() { - const OrmConfig = getMikroOrmConfig(mikroOrmEntities, pathToMigrations) + const OrmConfig = getMikroOrmConfig( + this.mikroOrmEntities, + this.pathToMigrations, + this.schema + ) // Initializing the ORM this.orm = await MikroORM.init(OrmConfig) - if (this.orm === null) { - throw new Error("ORM not configured") - } + this.manager = this.orm.em - this.manager = await this.orm.em + try { + await this.orm.getSchemaGenerator().ensureDatabase() + } catch (err) {} - await this.orm.schema.refreshDatabase() // ensure db exists and is fresh + await this.manager?.execute( + `CREATE SCHEMA IF NOT EXISTS "${this.schema ?? "public"}";` + ) + + const pendingMigrations = await this.orm + .getMigrator() + .getPendingMigrations() + + if (pendingMigrations && pendingMigrations.length > 0) { + await this.orm + .getMigrator() + .up({ migrations: pendingMigrations.map((m) => m.name!) }) + } else { + await this.orm.schema.refreshDatabase() // ensure db exists and is fresh + } }, async clearDatabase() { @@ -102,7 +120,17 @@ export function getMikroOrmWrapper( throw new Error("ORM not configured") } - await this.orm.close() + await this.manager?.execute( + `DROP SCHEMA IF EXISTS "${this.schema ?? "public"}" CASCADE;` + ) + + await this.manager?.execute( + `CREATE SCHEMA IF NOT EXISTS "${this.schema ?? "public"}";` + ) + + try { + await this.orm.close() + } catch {} this.orm = null this.manager = null diff --git a/packages/utils/src/common/create-psql-index-helper.ts b/packages/utils/src/common/create-psql-index-helper.ts index 7aa35a676d0e7..642370c08a58a 100644 --- a/packages/utils/src/common/create-psql-index-helper.ts +++ b/packages/utils/src/common/create-psql-index-helper.ts @@ -45,7 +45,10 @@ export function createPsqlIndexStatementHelper({ columns = Array.isArray(columns) ? columns.join(", ") : columns const typeStr = type ? ` USING ${type}` : "" const optionsStr = where ? ` WHERE ${where}` : "" - const uniqueStr = unique ? " UNIQUE" : "" - return `CREATE${uniqueStr} INDEX IF NOT EXISTS ${name} ON ${tableName}${typeStr} (${columns})${optionsStr}` + if (!unique) { + return `CREATE INDEX IF NOT EXISTS "${name}" ON "${tableName}"${typeStr} (${columns})${optionsStr}` + } else { + return `ALTER TABLE IF EXISTS "${tableName}" ADD CONSTRAINT "${name} UNIQUE"(${columns})` + } } From f52f6a8df0ae8a021f4ff537883642ac8c0c33ca Mon Sep 17 00:00:00 2001 From: adrien2p Date: Mon, 12 Feb 2024 16:09:06 +0100 Subject: [PATCH 08/29] fixes --- .../fulfillment-module-service.spec.ts | 6 +- .../.snapshot-medusa-fulfillment.json | 60 ++--- .../src/migrations/Migration20240212133507.ts | 207 ------------------ .../src/migrations/Migration20240212150706.ts | 88 ++++++++ .../src/common/create-psql-index-helper.ts | 2 +- 5 files changed, 122 insertions(+), 241 deletions(-) delete mode 100644 packages/fulfillment/src/migrations/Migration20240212133507.ts create mode 100644 packages/fulfillment/src/migrations/Migration20240212150706.ts diff --git a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts index d17463bcc07ee..d47366f7b3e02 100644 --- a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts +++ b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts @@ -304,10 +304,10 @@ describe("fulfillment module service", function () { } await service.create(data) - await service.create(data) + const err = await service.create(data).catch((e) => e) - const ful = await service.list({}) - console.log(JSON.stringify(ful, null, 2)) + expect(err).toBeDefined() + expect(err.constraint).toBe("IDX_fulfillment_set_name_unique") }) }) }) diff --git a/packages/fulfillment/src/migrations/.snapshot-medusa-fulfillment.json b/packages/fulfillment/src/migrations/.snapshot-medusa-fulfillment.json index 6020fa02dc4da..b4ab9e2b4651f 100644 --- a/packages/fulfillment/src/migrations/.snapshot-medusa-fulfillment.json +++ b/packages/fulfillment/src/migrations/.snapshot-medusa-fulfillment.json @@ -167,7 +167,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "CREATE INDEX IF NOT EXISTS IDX_fulfillment_address_fulfillment_id ON fulfillment_address (fulfillment_id) WHERE deleted_at IS NULL" + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_fulfillment_address_fulfillment_id\" ON \"fulfillment_address\" (fulfillment_id) WHERE deleted_at IS NULL" }, { "keyName": "IDX_fulfillment_address_deleted_at", @@ -177,7 +177,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "CREATE INDEX IF NOT EXISTS IDX_fulfillment_address_deleted_at ON fulfillment_address (deleted_at) WHERE deleted_at IS NOT NULL" + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_fulfillment_address_deleted_at\" ON \"fulfillment_address\" (deleted_at) WHERE deleted_at IS NOT NULL" }, { "keyName": "fulfillment_address_pkey", @@ -274,7 +274,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "CREATE UNIQUE INDEX IF NOT EXISTS IDX_fulfillment_set_name_unique ON fulfillment_set (name) WHERE deleted_at IS NOT NULL" + "expression": "ALTER TABLE IF EXISTS \"fulfillment_set\" ADD CONSTRAINT \"IDX_fulfillment_set_name_unique\" UNIQUE (name)" }, { "keyName": "IDX_fulfillment_set_deleted_at", @@ -284,7 +284,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "CREATE INDEX IF NOT EXISTS IDX_fulfillment_set_deleted_at ON fulfillment_set (deleted_at) WHERE deleted_at IS NOT NULL" + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_fulfillment_set_deleted_at\" ON \"fulfillment_set\" (deleted_at) WHERE deleted_at IS NOT NULL" }, { "keyName": "fulfillment_set_pkey", @@ -415,7 +415,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "CREATE INDEX IF NOT EXISTS IDX_geo_zone_country_code ON geo_zone (country_code) WHERE deleted_at IS NULL" + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_geo_zone_country_code\" ON \"geo_zone\" (country_code) WHERE deleted_at IS NULL" }, { "keyName": "IDX_geo_zone_province_code", @@ -425,7 +425,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "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" + "expression": "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" }, { "keyName": "IDX_geo_zone_city", @@ -435,7 +435,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "CREATE INDEX IF NOT EXISTS IDX_geo_zone_city ON geo_zone (city) WHERE deleted_at IS NULL AND city IS NOT NULL" + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_geo_zone_city\" ON \"geo_zone\" (city) WHERE deleted_at IS NULL AND city IS NOT NULL" }, { "keyName": "IDX_geo_zone_deleted_at", @@ -445,7 +445,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "CREATE INDEX IF NOT EXISTS IDX_geo_zone_deleted_at ON geo_zone (deleted_at) WHERE deleted_at IS NOT NULL" + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_geo_zone_deleted_at\" ON \"geo_zone\" (deleted_at) WHERE deleted_at IS NOT NULL" }, { "keyName": "geo_zone_pkey", @@ -524,7 +524,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "CREATE INDEX IF NOT EXISTS IDX_service_provider_deleted_at ON service_provider (deleted_at) WHERE deleted_at IS NOT NULL" + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_service_provider_deleted_at\" ON \"service_provider\" (deleted_at) WHERE deleted_at IS NOT NULL" }, { "keyName": "service_provider_pkey", @@ -612,7 +612,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "CREATE UNIQUE INDEX IF NOT EXISTS IDX_service_zone_name_unique ON service_zone (name) WHERE deleted_at IS NOT NULL" + "expression": "ALTER TABLE IF EXISTS \"service_zone\" ADD CONSTRAINT \"IDX_service_zone_name_unique\" UNIQUE (name)" }, { "keyName": "IDX_service_zone_deleted_at", @@ -622,7 +622,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "CREATE INDEX IF NOT EXISTS IDX_service_zone_deleted_at ON service_zone (deleted_at) WHERE deleted_at IS NOT NULL" + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_service_zone_deleted_at\" ON \"service_zone\" (deleted_at) WHERE deleted_at IS NOT NULL" }, { "keyName": "service_zone_pkey", @@ -858,7 +858,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "CREATE INDEX IF NOT EXISTS IDX_shipping_option_type_shipping_option_id ON shipping_option_type (shipping_option_id) WHERE deleted_at IS NULL" + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_shipping_option_type_shipping_option_id\" ON \"shipping_option_type\" (shipping_option_id) WHERE deleted_at IS NULL" }, { "keyName": "IDX_shipping_option_type_deleted_at", @@ -868,7 +868,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "CREATE INDEX IF NOT EXISTS IDX_shipping_option_type_deleted_at ON shipping_option_type (deleted_at) WHERE deleted_at IS NOT NULL" + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_shipping_option_type_deleted_at\" ON \"shipping_option_type\" (deleted_at) WHERE deleted_at IS NOT NULL" }, { "keyName": "shipping_option_type_pkey", @@ -947,7 +947,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "CREATE INDEX IF NOT EXISTS IDX_shipping_profile_deleted_at ON shipping_profile (deleted_at) WHERE deleted_at IS NOT NULL" + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_shipping_profile_deleted_at\" ON \"shipping_profile\" (deleted_at) WHERE deleted_at IS NOT NULL" }, { "keyName": "shipping_profile_pkey", @@ -1103,7 +1103,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "CREATE INDEX IF NOT EXISTS IDX_shipping_option_service_zone_id ON shipping_option (service_zone_id) WHERE deleted_at IS NULL" + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_shipping_option_service_zone_id\" ON \"shipping_option\" (service_zone_id) WHERE deleted_at IS NULL" }, { "keyName": "IDX_shipping_option_shipping_profile_id", @@ -1113,7 +1113,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "CREATE INDEX IF NOT EXISTS IDX_shipping_option_shipping_profile_id ON shipping_option (shipping_profile_id) WHERE deleted_at IS NULL" + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_shipping_option_shipping_profile_id\" ON \"shipping_option\" (shipping_profile_id) WHERE deleted_at IS NULL" }, { "keyName": "IDX_shipping_option_service_provider_id", @@ -1123,7 +1123,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "CREATE INDEX IF NOT EXISTS IDX_shipping_option_service_provider_id ON shipping_option (service_provider_id) WHERE deleted_at IS NULL" + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_shipping_option_service_provider_id\" ON \"shipping_option\" (service_provider_id) WHERE deleted_at IS NULL" }, { "keyName": "IDX_shipping_option_shipping_option_type_id", @@ -1133,7 +1133,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "CREATE INDEX IF NOT EXISTS IDX_shipping_option_shipping_option_type_id ON shipping_option (shipping_option_type_id) WHERE deleted_at IS NULL" + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_shipping_option_shipping_option_type_id\" ON \"shipping_option\" (shipping_option_type_id) WHERE deleted_at IS NULL" }, { "keyName": "IDX_shipping_option_deleted_at", @@ -1143,7 +1143,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "CREATE INDEX IF NOT EXISTS IDX_shipping_option_deleted_at ON shipping_option (deleted_at) WHERE deleted_at IS NOT NULL" + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_shipping_option_deleted_at\" ON \"shipping_option\" (deleted_at) WHERE deleted_at IS NOT NULL" }, { "keyName": "shipping_option_pkey", @@ -1298,7 +1298,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "CREATE INDEX IF NOT EXISTS IDX_shipping_option_rule_deleted_at ON shipping_option_rule (deleted_at) WHERE deleted_at IS NOT NULL" + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_shipping_option_rule_deleted_at\" ON \"shipping_option_rule\" (deleted_at) WHERE deleted_at IS NOT NULL" }, { "keyName": "shipping_option_rule_pkey", @@ -1484,7 +1484,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "CREATE INDEX IF NOT EXISTS IDX_fulfillment_location_id ON fulfillment (location_id) WHERE deleted_at IS NULL" + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_fulfillment_location_id\" ON \"fulfillment\" (location_id) WHERE deleted_at IS NULL" }, { "keyName": "IDX_fulfillment_provider_id", @@ -1494,7 +1494,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "CREATE INDEX IF NOT EXISTS IDX_fulfillment_provider_id ON fulfillment (provider_id) WHERE deleted_at IS NULL" + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_fulfillment_provider_id\" ON \"fulfillment\" (provider_id) WHERE deleted_at IS NULL" }, { "keyName": "IDX_fulfillment_shipping_option_id", @@ -1504,7 +1504,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "CREATE INDEX IF NOT EXISTS IDX_fulfillment_shipping_option_id ON fulfillment (shipping_option_id) WHERE deleted_at IS NULL" + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_fulfillment_shipping_option_id\" ON \"fulfillment\" (shipping_option_id) WHERE deleted_at IS NULL" }, { "keyName": "IDX_fulfillment_deleted_at", @@ -1514,7 +1514,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "CREATE INDEX IF NOT EXISTS IDX_fulfillment_deleted_at ON fulfillment (deleted_at) WHERE deleted_at IS NOT NULL" + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_fulfillment_deleted_at\" ON \"fulfillment\" (deleted_at) WHERE deleted_at IS NOT NULL" }, { "keyName": "fulfillment_pkey", @@ -1670,7 +1670,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "CREATE INDEX IF NOT EXISTS IDX_fulfillment_label_fulfillment_id ON fulfillment_label (fulfillment_id) WHERE deleted_at IS NULL" + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_fulfillment_label_fulfillment_id\" ON \"fulfillment_label\" (fulfillment_id) WHERE deleted_at IS NULL" }, { "keyName": "IDX_fulfillment_label_deleted_at", @@ -1680,7 +1680,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "CREATE INDEX IF NOT EXISTS IDX_fulfillment_label_deleted_at ON fulfillment_label (deleted_at) WHERE deleted_at IS NOT NULL" + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_fulfillment_label_deleted_at\" ON \"fulfillment_label\" (deleted_at) WHERE deleted_at IS NOT NULL" }, { "keyName": "fulfillment_label_pkey", @@ -1826,7 +1826,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "CREATE INDEX IF NOT EXISTS IDX_fulfillment_item_fulfillment_id ON fulfillment_item (line_item_id) WHERE deleted_at IS NULL" + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_fulfillment_item_fulfillment_id\" ON \"fulfillment_item\" (line_item_id) WHERE deleted_at IS NULL" }, { "keyName": "IDX_fulfillment_item_inventory_item_id", @@ -1836,7 +1836,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "CREATE INDEX IF NOT EXISTS IDX_fulfillment_item_fulfillment_id ON fulfillment_item (inventory_item_id) WHERE deleted_at IS NULL" + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_fulfillment_item_fulfillment_id\" ON \"fulfillment_item\" (inventory_item_id) WHERE deleted_at IS NULL" }, { "keyName": "IDX_fulfillment_item_fulfillment_id", @@ -1846,7 +1846,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "CREATE INDEX IF NOT EXISTS IDX_fulfillment_item_fulfillment_id ON fulfillment_item (fulfillment_id) WHERE deleted_at IS NULL" + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_fulfillment_item_fulfillment_id\" ON \"fulfillment_item\" (fulfillment_id) WHERE deleted_at IS NULL" }, { "keyName": "IDX_fulfillment_item_deleted_at", @@ -1856,7 +1856,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "CREATE INDEX IF NOT EXISTS IDX_fulfillment_item_deleted_at ON fulfillment_item (deleted_at) WHERE deleted_at IS NOT NULL" + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_fulfillment_item_deleted_at\" ON \"fulfillment_item\" (deleted_at) WHERE deleted_at IS NOT NULL" }, { "keyName": "fulfillment_item_pkey", diff --git a/packages/fulfillment/src/migrations/Migration20240212133507.ts b/packages/fulfillment/src/migrations/Migration20240212133507.ts deleted file mode 100644 index 6a9abd4efe737..0000000000000 --- a/packages/fulfillment/src/migrations/Migration20240212133507.ts +++ /dev/null @@ -1,207 +0,0 @@ -import { Migration } from "@mikro-orm/migrations" - -export class Migration20240212133507 extends Migration { - async up(): Promise { - this.addSql( - 'create table if not exists "fulfillment_address" ("id" text not null, "fulfillment_id" text 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_fulfillment_id ON fulfillment_address (fulfillment_id) WHERE deleted_at IS NULL;" - ) - 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_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 IDX_fulfillment_set_name_unique ON fulfillment_set (name) WHERE deleted_at IS NOT NULL;" - "alter table fulfillment_set add constraint IDX_fulfillment_set_name_unique UNIQUE (name)" - ) - 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 "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, "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_deleted_at ON geo_zone (deleted_at) WHERE deleted_at IS NOT NULL;" - ) - - this.addSql( - 'create table if not exists "service_provider" ("id" 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 "service_provider_pkey" primary key ("id"));' - ) - this.addSql( - "CREATE INDEX IF NOT EXISTS IDX_service_provider_deleted_at ON service_provider (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, "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 NOT 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 "service_zone_geo_zones" ("service_zone_id" text not null, "geo_zone_id" text not null, constraint "service_zone_geo_zones_pkey" primary key ("service_zone_id", "geo_zone_id"));' - ) - - this.addSql( - 'create table if not exists "fulfillment_set_service_zones" ("fulfillment_set_id" text not null, "service_zone_id" text not null, constraint "fulfillment_set_service_zones_pkey" primary key ("fulfillment_set_id", "service_zone_id"));' - ) - - 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, "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_type_pkey" primary key ("id"));' - ) - this.addSql( - "CREATE INDEX IF NOT EXISTS IDX_shipping_option_type_shipping_option_id ON shipping_option_type (shipping_option_id) WHERE deleted_at IS NULL;" - ) - 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, "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 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 not null, "service_provider_id" text not null, "shipping_option_type_id" text null, "data" jsonb null, "metadata" jsonb 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_service_provider_id ON shipping_option (service_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 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_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 not null, "shipping_option_id" text null, "metadata" jsonb null, "delivery_address_id" text not null, "items_id" text not 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( - "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, "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_fulfillment_id ON fulfillment_item (line_item_id) WHERE deleted_at IS NULL;" - ) - this.addSql( - "CREATE INDEX IF NOT EXISTS IDX_fulfillment_item_fulfillment_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_geo_zones" add constraint "service_zone_geo_zones_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 "service_zone_geo_zones" add constraint "service_zone_geo_zones_geo_zone_id_foreign" foreign key ("geo_zone_id") references "geo_zone" ("id") on update cascade on delete cascade;' - ) - - this.addSql( - 'alter table if exists "fulfillment_set_service_zones" add constraint "fulfillment_set_service_zones_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 "fulfillment_set_service_zones" add constraint "fulfillment_set_service_zones_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;' - ) - 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;' - ) - this.addSql( - 'alter table if exists "shipping_option" add constraint "shipping_option_service_provider_id_foreign" foreign key ("service_provider_id") references "service_provider" ("id") on update cascade;' - ) - 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;' - ) - - 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;' - ) - - 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_provider_id_foreign" foreign key ("provider_id") references "service_provider" ("id") on update cascade;' - ) - 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;' - ) - this.addSql( - 'alter table if exists "fulfillment" add constraint "fulfillment_items_id_foreign" foreign key ("items_id") references "fulfillment_item" ("id") on update 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;' - ) - - 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;' - ) - } -} diff --git a/packages/fulfillment/src/migrations/Migration20240212150706.ts b/packages/fulfillment/src/migrations/Migration20240212150706.ts new file mode 100644 index 0000000000000..8b159fc11eec2 --- /dev/null +++ b/packages/fulfillment/src/migrations/Migration20240212150706.ts @@ -0,0 +1,88 @@ +import { Migration } from '@mikro-orm/migrations'; + +export class Migration20240212150706 extends Migration { + + async up(): Promise { + this.addSql('create table if not exists "fulfillment_address" ("id" text not null, "fulfillment_id" text 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_fulfillment_id" ON "fulfillment_address" (fulfillment_id) WHERE deleted_at IS NULL;'); + 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_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('ALTER TABLE IF EXISTS "fulfillment_set" ADD CONSTRAINT "IDX_fulfillment_set_name_unique" UNIQUE (name);'); + 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 "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, "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_deleted_at" ON "geo_zone" (deleted_at) WHERE deleted_at IS NOT NULL;'); + + this.addSql('create table if not exists "service_provider" ("id" 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 "service_provider_pkey" primary key ("id"));'); + this.addSql('CREATE INDEX IF NOT EXISTS "IDX_service_provider_deleted_at" ON "service_provider" (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, "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('ALTER TABLE IF EXISTS "service_zone" ADD CONSTRAINT "IDX_service_zone_name_unique" UNIQUE (name);'); + 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 "service_zone_geo_zones" ("service_zone_id" text not null, "geo_zone_id" text not null, constraint "service_zone_geo_zones_pkey" primary key ("service_zone_id", "geo_zone_id"));'); + + this.addSql('create table if not exists "fulfillment_set_service_zones" ("fulfillment_set_id" text not null, "service_zone_id" text not null, constraint "fulfillment_set_service_zones_pkey" primary key ("fulfillment_set_id", "service_zone_id"));'); + + 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, "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_type_pkey" primary key ("id"));'); + this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_type_shipping_option_id" ON "shipping_option_type" (shipping_option_id) WHERE deleted_at IS NULL;'); + 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, "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 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 not null, "service_provider_id" text not null, "shipping_option_type_id" text null, "data" jsonb null, "metadata" jsonb 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_service_provider_id" ON "shipping_option" (service_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 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_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 not null, "shipping_option_id" text null, "metadata" jsonb null, "delivery_address_id" text not null, "items_id" text not 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('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, "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_fulfillment_id" ON "fulfillment_item" (line_item_id) WHERE deleted_at IS NULL;'); + this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_item_fulfillment_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_geo_zones" add constraint "service_zone_geo_zones_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 "service_zone_geo_zones" add constraint "service_zone_geo_zones_geo_zone_id_foreign" foreign key ("geo_zone_id") references "geo_zone" ("id") on update cascade on delete cascade;'); + + this.addSql('alter table if exists "fulfillment_set_service_zones" add constraint "fulfillment_set_service_zones_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 "fulfillment_set_service_zones" add constraint "fulfillment_set_service_zones_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;'); + 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;'); + this.addSql('alter table if exists "shipping_option" add constraint "shipping_option_service_provider_id_foreign" foreign key ("service_provider_id") references "service_provider" ("id") on update cascade;'); + 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;'); + + 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;'); + + 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_provider_id_foreign" foreign key ("provider_id") references "service_provider" ("id") on update cascade;'); + 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;'); + this.addSql('alter table if exists "fulfillment" add constraint "fulfillment_items_id_foreign" foreign key ("items_id") references "fulfillment_item" ("id") on update 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;'); + + 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;'); + } + +} diff --git a/packages/utils/src/common/create-psql-index-helper.ts b/packages/utils/src/common/create-psql-index-helper.ts index 642370c08a58a..2c023a4bd005b 100644 --- a/packages/utils/src/common/create-psql-index-helper.ts +++ b/packages/utils/src/common/create-psql-index-helper.ts @@ -49,6 +49,6 @@ export function createPsqlIndexStatementHelper({ if (!unique) { return `CREATE INDEX IF NOT EXISTS "${name}" ON "${tableName}"${typeStr} (${columns})${optionsStr}` } else { - return `ALTER TABLE IF EXISTS "${tableName}" ADD CONSTRAINT "${name} UNIQUE"(${columns})` + return `ALTER TABLE IF EXISTS "${tableName}" ADD CONSTRAINT "${name}" UNIQUE (${columns})` } } From 6050c0f615e7cbbce94864777faf32a79c085045 Mon Sep 17 00:00:00 2001 From: adrien2p Date: Mon, 12 Feb 2024 16:53:22 +0100 Subject: [PATCH 09/29] fixes --- ...12150706.ts => Migration20240212155252.ts} | 2 +- .../__tests__/create-psql-index-helper.ts | 31 ++++++++++++++----- .../src/common/create-psql-index-helper.ts | 2 +- 3 files changed, 26 insertions(+), 9 deletions(-) rename packages/fulfillment/src/migrations/{Migration20240212150706.ts => Migration20240212155252.ts} (99%) diff --git a/packages/fulfillment/src/migrations/Migration20240212150706.ts b/packages/fulfillment/src/migrations/Migration20240212155252.ts similarity index 99% rename from packages/fulfillment/src/migrations/Migration20240212150706.ts rename to packages/fulfillment/src/migrations/Migration20240212155252.ts index 8b159fc11eec2..dde6a90b814d4 100644 --- a/packages/fulfillment/src/migrations/Migration20240212150706.ts +++ b/packages/fulfillment/src/migrations/Migration20240212155252.ts @@ -1,6 +1,6 @@ import { Migration } from '@mikro-orm/migrations'; -export class Migration20240212150706 extends Migration { +export class Migration20240212155252 extends Migration { async up(): Promise { this.addSql('create table if not exists "fulfillment_address" ("id" text not null, "fulfillment_id" text 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"));'); diff --git a/packages/utils/src/common/__tests__/create-psql-index-helper.ts b/packages/utils/src/common/__tests__/create-psql-index-helper.ts index 56166a2656e82..ec2c70178e80f 100644 --- a/packages/utils/src/common/__tests__/create-psql-index-helper.ts +++ b/packages/utils/src/common/__tests__/create-psql-index-helper.ts @@ -10,7 +10,7 @@ describe("createPsqlIndexStatementHelper", function () { const indexStatement = createPsqlIndexStatementHelper(options) expect(indexStatement).toEqual( - `CREATE INDEX IF NOT EXISTS ${options.name} ON ${options.tableName} (${options.columns})` + `CREATE INDEX IF NOT EXISTS "${options.name}" ON "${options.tableName}" (${options.columns})` ) }) @@ -23,9 +23,9 @@ describe("createPsqlIndexStatementHelper", function () { const indexStatement = createPsqlIndexStatementHelper(options) expect(indexStatement).toEqual( - `CREATE INDEX IF NOT EXISTS ${options.name} ON ${ + `CREATE INDEX IF NOT EXISTS "${options.name}" ON "${ options.tableName - } (${options.columns.join(", ")})` + }" (${options.columns.join(", ")})` ) }) @@ -39,9 +39,9 @@ describe("createPsqlIndexStatementHelper", function () { const indexStatement = createPsqlIndexStatementHelper(options) expect(indexStatement).toEqual( - `CREATE INDEX IF NOT EXISTS ${options.name} ON ${ + `CREATE INDEX IF NOT EXISTS "${options.name}" ON "${ options.tableName - } (${options.columns.join(", ")}) WHERE ${options.where}` + }" (${options.columns.join(", ")}) WHERE ${options.where}` ) }) @@ -56,9 +56,26 @@ describe("createPsqlIndexStatementHelper", function () { const indexStatement = createPsqlIndexStatementHelper(options) expect(indexStatement).toEqual( - `CREATE INDEX IF NOT EXISTS ${options.name} ON ${ + `CREATE INDEX IF NOT EXISTS "${options.name}" ON "${ options.tableName - } USING GIN (${options.columns.join(", ")}) WHERE ${options.where}` + }" USING GIN (${options.columns.join(", ")}) WHERE ${options.where}` + ) + }) + + it("should generate unique constraint", function () { + const options = { + name: "index_name", + tableName: "table_name", + columns: ["column_name_1", "column_name_2"], + unique: true, + where: "column_name_1 IS NOT NULL", + } + + const indexStatement = createPsqlIndexStatementHelper(options) + expect(indexStatement).toEqual( + `ALTER TABLE IF EXISTS "${options.tableName}" ADD CONSTRAINT "${ + options.name + }" UNIQUE (${options.columns.join(", ")}) WHERE ${options.where}` ) }) }) diff --git a/packages/utils/src/common/create-psql-index-helper.ts b/packages/utils/src/common/create-psql-index-helper.ts index 2c023a4bd005b..8d7529d58224f 100644 --- a/packages/utils/src/common/create-psql-index-helper.ts +++ b/packages/utils/src/common/create-psql-index-helper.ts @@ -49,6 +49,6 @@ export function createPsqlIndexStatementHelper({ if (!unique) { return `CREATE INDEX IF NOT EXISTS "${name}" ON "${tableName}"${typeStr} (${columns})${optionsStr}` } else { - return `ALTER TABLE IF EXISTS "${tableName}" ADD CONSTRAINT "${name}" UNIQUE (${columns})` + return `ALTER TABLE IF EXISTS "${tableName}" ADD CONSTRAINT "${name}" UNIQUE (${columns})${optionsStr}` } } From 00c8ca65318f21951c2532bfa3e3ffda8282d6b4 Mon Sep 17 00:00:00 2001 From: adrien2p Date: Mon, 12 Feb 2024 16:55:39 +0100 Subject: [PATCH 10/29] fixes --- .../fulfillment-module-service.spec.ts | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts index d47366f7b3e02..a617009469146 100644 --- a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts +++ b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts @@ -126,7 +126,7 @@ describe("fulfillment module service", function () { type: "test-type2", service_zones: [ { - name: "test", + name: "test2", }, ], }, @@ -135,7 +135,7 @@ describe("fulfillment module service", function () { type: "test-type3", service_zones: [ { - name: "test2", + name: "test3", }, ], }, @@ -162,11 +162,6 @@ describe("fulfillment module service", function () { ) ++i } - - // The two first fulfillment sets share the same service zone - expect(fulfillmentSets[0].service_zones[0].id).toEqual( - fulfillmentSets[1].service_zones[0].id - ) }) it("should create a new fulfillment set with new service zones and new geo zones", async function () { @@ -233,7 +228,7 @@ describe("fulfillment module service", function () { type: "test-type2", service_zones: [ { - name: "test", + name: "test2", geo_zones: [ { type: GeoZoneType.COUNTRY, @@ -248,7 +243,7 @@ describe("fulfillment module service", function () { type: "test-type3", service_zones: [ { - name: "test2", + name: "test3", geo_zones: [ { type: GeoZoneType.CITY, @@ -289,11 +284,6 @@ describe("fulfillment module service", function () { ) ++i } - - // The two first fulfillment sets share the same geo zone for their service zones - expect(fulfillmentSets[0].service_zones[0].geo_zones[0].id).toEqual( - fulfillmentSets[1].service_zones[0].geo_zones[0].id - ) }) describe("should fail", () => { From b89cfe8817902ee090a00c19de427e3117e769ff Mon Sep 17 00:00:00 2001 From: adrien2p Date: Tue, 13 Feb 2024 10:06:05 +0100 Subject: [PATCH 11/29] continue creation flow --- .../fulfillment-module-service.spec.ts | 14 +- .../services/fulfillment-module-service.ts | 213 +++++++++++++++--- 2 files changed, 190 insertions(+), 37 deletions(-) diff --git a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts index a617009469146..45a41b4819fdc 100644 --- a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts +++ b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts @@ -126,7 +126,7 @@ describe("fulfillment module service", function () { type: "test-type2", service_zones: [ { - name: "test2", + name: "test", }, ], }, @@ -135,7 +135,7 @@ describe("fulfillment module service", function () { type: "test-type3", service_zones: [ { - name: "test3", + name: "test2", }, ], }, @@ -162,6 +162,11 @@ describe("fulfillment module service", function () { ) ++i } + + // expect the first and second fulfillment set to have the same service zone + expect(fulfillmentSets[0].service_zones[0].id).toEqual( + fulfillmentSets[1].service_zones[0].id + ) }) it("should create a new fulfillment set with new service zones and new geo zones", async function () { @@ -284,6 +289,11 @@ describe("fulfillment module service", function () { ) ++i } + + // expect the first and second fulfillment set to have the same geo zone for their service zone + expect(fulfillmentSets[0].service_zones[0].geo_zones[0].id).toEqual( + fulfillmentSets[1].service_zones[0].geo_zones[0].id + ) }) describe("should fail", () => { diff --git a/packages/fulfillment/src/services/fulfillment-module-service.ts b/packages/fulfillment/src/services/fulfillment-module-service.ts index 34afa3f51a4eb..f20fb381616c9 100644 --- a/packages/fulfillment/src/services/fulfillment-module-service.ts +++ b/packages/fulfillment/src/services/fulfillment-module-service.ts @@ -8,7 +8,11 @@ import { ModulesSdkTypes, UpdateFulfillmentSetDTO, } from "@medusajs/types" -import { InjectTransactionManager, ModulesSdkUtils } from "@medusajs/utils" +import { + InjectTransactionManager, + ModulesSdkUtils, + promiseAll, +} from "@medusajs/utils" import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config" import { FulfillmentSet, GeoZone, ServiceZone, ShippingOption } from "@models" @@ -82,11 +86,7 @@ export default class FulfillmentModuleService< ): Promise< FulfillmentTypes.FulfillmentSetDTO | FulfillmentTypes.FulfillmentSetDTO[] > { - const data_: FulfillmentTypes.CreateFulfillmentSetDTO[] = Array.isArray( - data - ) - ? data - : [data] + const data_ = Array.isArray(data) ? data : [data] const fulfillmentSetMap = new Map< string, @@ -101,31 +101,112 @@ export default class FulfillmentModuleService< > >() - const serviceZoneToCreate: FulfillmentTypes.CreateServiceZoneDTO[] = [] + const serviceZoneGeoZonesMap = new Map< + string, + Map + >() + + const serviceZonesToCreate: FulfillmentTypes.CreateServiceZoneDTO[] = [] + const geoZonesToCreate: FulfillmentTypes.CreateGeoZoneDTO[] = [] + let serviceZoneIds: string[] = [] + let geoZoneIds: string[] = [] + + data_.forEach(({ service_zones }) => { + service_zones?.forEach((serviceZone) => { + if ("id" in serviceZone) { + serviceZoneIds.push(serviceZone.id) + } + + if ("geo_zones" in serviceZone && serviceZone.geo_zones) { + serviceZone.geo_zones.forEach((geoZone) => { + if ("id" in geoZone) { + geoZoneIds.push(geoZone.id) + } + }) + } + }) + }) - const serviceZoneIds = data_ - .map(({ service_zones }) => service_zones?.map(({ id }: any) => id)) - .flat() - .filter(Boolean) + serviceZoneIds = serviceZoneIds.filter(Boolean) + geoZoneIds = geoZoneIds.filter(Boolean) let existingServiceZones: TServiceZoneEntity[] = [] let existingServiceZonesMap = new Map() + let existingGeoZones: TGeoZoneEntity[] = [] + let existingGeoZonesMap = new Map() + const promises: Promise[] = [] + if (serviceZoneIds.length) { - existingServiceZones = await this.serviceZoneService_.list( - { - id: serviceZoneIds, - }, - { - select: ["id", "name"], - }, - sharedContext + promises.push( + this.serviceZoneService_ + .list( + { + id: serviceZoneIds, + }, + { + select: ["id", "name"], + }, + sharedContext + ) + .then((serviceZones) => { + existingServiceZones = serviceZones + existingServiceZonesMap = new Map( + existingServiceZones.map((serviceZone) => [ + serviceZone.id, + serviceZone, + ]) + ) + }) ) + } - existingServiceZonesMap = new Map( - existingServiceZones.map((serviceZone) => [serviceZone.id, serviceZone]) + if (geoZoneIds.length) { + promises.push( + this.geoZoneService_ + .list( + { + id: geoZoneIds, + }, + {}, + sharedContext + ) + .then((geoZones) => { + existingGeoZones = geoZones + existingGeoZonesMap = new Map( + existingGeoZones.map((geoZone) => [geoZone.id, geoZone]) + ) + }) ) } + await promiseAll(promises) + + const getGeoZoneIdentifier = ( + geoZone: FulfillmentTypes.CreateGeoZoneDTO | { id: string }, + { preventIdUsage = false }: { preventIdUsage?: boolean } = {} + ) => { + if (!preventIdUsage && "id" in geoZone) { + return geoZone.id + } + + let identifier = ("type" in geoZone && geoZone.type) || "" + + if ("country_code" in geoZone && geoZone.country_code) { + identifier += geoZone.country_code + } + if ("province_code" in geoZone && geoZone.province_code) { + identifier += geoZone.province_code + } + if ("city" in geoZone && geoZone.city) { + identifier += geoZone.city + } + if ("postal_expression" in geoZone && geoZone.postal_expression) { + identifier += JSON.stringify(geoZone.postal_expression) + } + + return identifier + } + data_.forEach(({ service_zones, ...fulfillmentSetDataOnly }) => { fulfillmentSetMap.set(fulfillmentSetDataOnly.name, fulfillmentSetDataOnly) @@ -133,8 +214,8 @@ export default class FulfillmentModuleService< * If there is any service zone to process * store the service zones to create while populating the fulfillment set service zone map * in order to be able after creating the service zones to re update the map with the - * newly create service zones and then assign them to the fulfillment sets to be - * to create. + * newly created service zones and then assign them to the fulfillment sets to be + * create attached. */ if (service_zones?.length) { @@ -142,17 +223,46 @@ export default class FulfillmentModuleService< string, Required["service_zones"][number] ][] = service_zones.map((serviceZone) => { + let geoZoneTuple: [ + string, + FulfillmentTypes.CreateGeoZoneDTO | { id: string } + ][] = [] + + if ("geo_zones" in serviceZone && serviceZone.geo_zones) { + const geo_zones = serviceZone.geo_zones + delete serviceZone.geo_zones + + geoZoneTuple = geo_zones.map((geoZone) => { + let existingGeoZone = + "id" in geoZone ? existingGeoZonesMap.get(geoZone.id)! : null + + if (!("id" in geoZone)) { + geoZonesToCreate.push(geoZone) + } + + const geoZoneIdentifier = getGeoZoneIdentifier(geoZone) + + return [geoZoneIdentifier, existingGeoZone ?? geoZone] + }) + } + let existingZone = "id" in serviceZone ? existingServiceZonesMap.get(serviceZone.id)! : null + if (!("id" in serviceZone)) { - serviceZoneToCreate.push(serviceZone) + serviceZonesToCreate.push(serviceZone) } const serviceZoneIdentifier = "id" in serviceZone ? serviceZone.id : serviceZone.name + serviceZoneGeoZonesMap.set( + serviceZoneIdentifier, + new Map(geoZoneTuple) + ) + return [serviceZoneIdentifier, existingZone ?? serviceZone] }) @@ -163,30 +273,63 @@ export default class FulfillmentModuleService< } }) - if (serviceZoneToCreate.length) { - const createdServiceZones = await this.serviceZoneService_.create( - serviceZoneToCreate, + if (geoZonesToCreate.length) { + // deduplicate geo zones to create + const geoZoneToCreateMap = new Map( + geoZonesToCreate.map((geoZone) => [ + getGeoZoneIdentifier(geoZone), + geoZone, + ]) + ) + const createdGeoZones = await this.geoZoneService_.create( + [...geoZoneToCreateMap.values()], sharedContext ) - const createdServiceZoneMap = new Map( - createdServiceZones.map((serviceZone: ServiceZone) => [ + + for (const [serviceZoneName, geoZoneMap] of serviceZoneGeoZonesMap) { + for (const createdGeoZone of createdGeoZones) { + const geoZoneIdentifier = getGeoZoneIdentifier(createdGeoZone, { + preventIdUsage: true, + }) + + if (geoZoneMap.has(geoZoneIdentifier)) { + geoZoneMap.set(geoZoneIdentifier, createdGeoZone) + } + } + + for (const serviceZone of serviceZonesToCreate) { + if (serviceZone.name === serviceZoneName) { + serviceZone.geo_zones = [...geoZoneMap.values()] + } + } + } + } + + if (serviceZonesToCreate.length) { + // Deduplicate service zones to create + const serviceZoneToCreateMap = new Map( + serviceZonesToCreate.map((serviceZone) => [ serviceZone.name, serviceZone, ]) ) + const createdServiceZones = await this.serviceZoneService_.create( + [...serviceZoneToCreateMap.values()], + sharedContext + ) for (const [ fulfillmentSetName, - serviceZoneToCreateMap, + serviceZoneMap, ] of fulfillmentSetServiceZonesMap) { - ;[...createdServiceZoneMap.values()].forEach((serviceZone) => { - if (serviceZoneToCreateMap.has(serviceZone.name)) { - serviceZoneToCreateMap.set(serviceZone.name, serviceZone) + for (const createdServiceZone of createdServiceZones) { + if (serviceZoneMap.has(createdServiceZone.name)) { + serviceZoneMap.set(createdServiceZone.name, createdServiceZone) } - }) + } const fulfillmentSet = fulfillmentSetMap.get(fulfillmentSetName)! - fulfillmentSet.service_zones = [...serviceZoneToCreateMap.values()] + fulfillmentSet.service_zones = [...serviceZoneMap.values()] fulfillmentSetMap.set(fulfillmentSetName, fulfillmentSet) } } From 9bf0c8217c86f7c3c3aec50e49a46e4f9f798ae8 Mon Sep 17 00:00:00 2001 From: adrien2p Date: Tue, 13 Feb 2024 10:14:00 +0100 Subject: [PATCH 12/29] cleanup --- .../services/fulfillment-module-service.ts | 175 +++++++++++------- 1 file changed, 111 insertions(+), 64 deletions(-) diff --git a/packages/fulfillment/src/services/fulfillment-module-service.ts b/packages/fulfillment/src/services/fulfillment-module-service.ts index f20fb381616c9..d229ff3dd2fac 100644 --- a/packages/fulfillment/src/services/fulfillment-module-service.ts +++ b/packages/fulfillment/src/services/fulfillment-module-service.ts @@ -68,50 +68,65 @@ export default class FulfillmentModuleService< return joinerConfig } - create( - data: FulfillmentTypes.CreateFulfillmentSetDTO[], - sharedContext?: Context - ): Promise - create( - data: FulfillmentTypes.CreateFulfillmentSetDTO, - sharedContext?: Context - ): Promise - - @InjectTransactionManager("baseRepository_") - async create( - data: - | FulfillmentTypes.CreateFulfillmentSetDTO - | FulfillmentTypes.CreateFulfillmentSetDTO[], - sharedContext?: Context - ): Promise< - FulfillmentTypes.FulfillmentSetDTO | FulfillmentTypes.FulfillmentSetDTO[] - > { - const data_ = Array.isArray(data) ? data : [data] + /** + * Returns the identifier of a geo zone. The identifier is a string that is + * generated based on the type of the geo zone and the properties of the geo + * zone. The identifier is used for map building and retrieval. + * + * @param geoZone + * @param preventIdUsage + * @protected + */ + protected static getGeoZoneIdentifier( + geoZone: Partial, + { preventIdUsage = false }: { preventIdUsage?: boolean } = {} + ): string { + if (!preventIdUsage && "id" in geoZone) { + return geoZone.id! + } - const fulfillmentSetMap = new Map< - string, - FulfillmentTypes.CreateFulfillmentSetDTO - >() + let identifier = ("type" in geoZone && geoZone.type) || "" - const fulfillmentSetServiceZonesMap = new Map< - string, - Map< - string, - Required["service_zones"][number] - > - >() + if ("country_code" in geoZone && geoZone.country_code) { + identifier += geoZone.country_code + } + if ("province_code" in geoZone && geoZone.province_code) { + identifier += geoZone.province_code + } + if ("city" in geoZone && geoZone.city) { + identifier += geoZone.city + } + if ("postal_expression" in geoZone && geoZone.postal_expression) { + identifier += JSON.stringify(geoZone.postal_expression) + } - const serviceZoneGeoZonesMap = new Map< - string, - Map - >() + return identifier + } - const serviceZonesToCreate: FulfillmentTypes.CreateServiceZoneDTO[] = [] - const geoZonesToCreate: FulfillmentTypes.CreateGeoZoneDTO[] = [] + /** + * Preparation step of the fulfillment set creation. This method is responsible for + * extracting the service zones and geo zones from the data and then from that + * data extract the ids of the service zones and geo zones that are already + * existing in the database. Then it will fetch the existing service zones and + * geo zones from the database and return them. + * + * @param data + * @param sharedContext + * @protected + */ + protected async prepareCreateData( + data: FulfillmentTypes.CreateFulfillmentSetDTO[], + sharedContext?: Context + ): Promise<{ + existingServiceZones: TServiceZoneEntity[] + existingServiceZonesMap: Map + existingGeoZones: TGeoZoneEntity[] + existingGeoZonesMap: Map + }> { let serviceZoneIds: string[] = [] let geoZoneIds: string[] = [] - data_.forEach(({ service_zones }) => { + data.forEach(({ service_zones }) => { service_zones?.forEach((serviceZone) => { if ("id" in serviceZone) { serviceZoneIds.push(serviceZone.id) @@ -181,31 +196,61 @@ export default class FulfillmentModuleService< await promiseAll(promises) - const getGeoZoneIdentifier = ( - geoZone: FulfillmentTypes.CreateGeoZoneDTO | { id: string }, - { preventIdUsage = false }: { preventIdUsage?: boolean } = {} - ) => { - if (!preventIdUsage && "id" in geoZone) { - return geoZone.id - } + return { + existingServiceZones, + existingServiceZonesMap, + existingGeoZones, + existingGeoZonesMap, + } + } - let identifier = ("type" in geoZone && geoZone.type) || "" + create( + data: FulfillmentTypes.CreateFulfillmentSetDTO[], + sharedContext?: Context + ): Promise + create( + data: FulfillmentTypes.CreateFulfillmentSetDTO, + sharedContext?: Context + ): Promise - if ("country_code" in geoZone && geoZone.country_code) { - identifier += geoZone.country_code - } - if ("province_code" in geoZone && geoZone.province_code) { - identifier += geoZone.province_code - } - if ("city" in geoZone && geoZone.city) { - identifier += geoZone.city - } - if ("postal_expression" in geoZone && geoZone.postal_expression) { - identifier += JSON.stringify(geoZone.postal_expression) - } + @InjectTransactionManager("baseRepository_") + async create( + data: + | FulfillmentTypes.CreateFulfillmentSetDTO + | FulfillmentTypes.CreateFulfillmentSetDTO[], + sharedContext?: Context + ): Promise< + FulfillmentTypes.FulfillmentSetDTO | FulfillmentTypes.FulfillmentSetDTO[] + > { + const data_ = Array.isArray(data) ? data : [data] - return identifier - } + const fulfillmentSetMap = new Map< + string, + FulfillmentTypes.CreateFulfillmentSetDTO + >() + + const fulfillmentSetServiceZonesMap = new Map< + string, + Map< + string, + Required["service_zones"][number] + > + >() + + const serviceZoneGeoZonesMap = new Map< + string, + Map + >() + + const serviceZonesToCreate: FulfillmentTypes.CreateServiceZoneDTO[] = [] + const geoZonesToCreate: FulfillmentTypes.CreateGeoZoneDTO[] = [] + + const { + existingServiceZones, + existingServiceZonesMap, + existingGeoZones, + existingGeoZonesMap, + } = await this.prepareCreateData(data_, sharedContext) data_.forEach(({ service_zones, ...fulfillmentSetDataOnly }) => { fulfillmentSetMap.set(fulfillmentSetDataOnly.name, fulfillmentSetDataOnly) @@ -240,7 +285,8 @@ export default class FulfillmentModuleService< geoZonesToCreate.push(geoZone) } - const geoZoneIdentifier = getGeoZoneIdentifier(geoZone) + const geoZoneIdentifier = + FulfillmentModuleService.getGeoZoneIdentifier(geoZone) return [geoZoneIdentifier, existingGeoZone ?? geoZone] }) @@ -277,7 +323,7 @@ export default class FulfillmentModuleService< // deduplicate geo zones to create const geoZoneToCreateMap = new Map( geoZonesToCreate.map((geoZone) => [ - getGeoZoneIdentifier(geoZone), + FulfillmentModuleService.getGeoZoneIdentifier(geoZone), geoZone, ]) ) @@ -288,9 +334,10 @@ export default class FulfillmentModuleService< for (const [serviceZoneName, geoZoneMap] of serviceZoneGeoZonesMap) { for (const createdGeoZone of createdGeoZones) { - const geoZoneIdentifier = getGeoZoneIdentifier(createdGeoZone, { - preventIdUsage: true, - }) + const geoZoneIdentifier = + FulfillmentModuleService.getGeoZoneIdentifier(createdGeoZone, { + preventIdUsage: true, + }) if (geoZoneMap.has(geoZoneIdentifier)) { geoZoneMap.set(geoZoneIdentifier, createdGeoZone) From af47507cbb6949287ec6788545b52027d2071c8f Mon Sep 17 00:00:00 2001 From: adrien2p Date: Tue, 13 Feb 2024 10:56:22 +0100 Subject: [PATCH 13/29] Add support to create service zones with geo zones --- .../fulfillment-module-service.spec.ts | 123 ++++++++++++++++-- .../services/fulfillment-module-service.ts | 100 +++++++++++++- 2 files changed, 209 insertions(+), 14 deletions(-) diff --git a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts index 45a41b4819fdc..5726eb3f65b4d 100644 --- a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts +++ b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts @@ -296,19 +296,122 @@ describe("fulfillment module service", function () { ) }) - describe("should fail", () => { - it(`on duplicated fulfillment set name`, async function () { - const data: CreateFulfillmentSetDTO = { + it(`should fail on duplicated fulfillment set name`, async function () { + const data: CreateFulfillmentSetDTO = { + name: "test", + type: "test-type", + } + + await service.create(data) + const err = await service.create(data).catch((e) => e) + + expect(err).toBeDefined() + expect(err.constraint).toBe("IDX_fulfillment_set_name_unique") + }) + }) + + describe("on create service zones", () => { + it("should create a new service zone", async function () { + const data = { + name: "test", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + ], + } + + const serviceZone = await service.createServiceZones(data) + + expect(serviceZone).toEqual( + expect.objectContaining({ + id: expect.any(String), + name: data.name, + geo_zones: expect.arrayContaining([ + expect.objectContaining({ + type: data.geo_zones[0].type, + country_code: data.geo_zones[0].country_code, + }), + ]), + }) + ) + }) + + it("should create a collection of service zones", async function () { + const data = [ + { name: "test", - type: "test-type", - } + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + ], + }, + { + name: "test2", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + ], + }, + { + name: "test3", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "uk", + }, + ], + }, + ] + + const serviceZones = await service.createServiceZones(data) + + expect(serviceZones).toHaveLength(3) + + let i = 0 + for (const data_ of data) { + expect(serviceZones[i]).toEqual( + expect.objectContaining({ + id: expect.any(String), + name: data_.name, + geo_zones: expect.arrayContaining([ + expect.objectContaining({ + type: data_.geo_zones[0].type, + country_code: data_.geo_zones[0].country_code, + }), + ]), + }) + ) + ++i + } + + // expect the first and second service zone to have the same geo zone + expect(serviceZones[0].geo_zones[0].id).toEqual( + serviceZones[1].geo_zones[0].id + ) + }) + + it("should fail on duplicated service zone name", async function () { + const data = { + name: "test", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + ], + } - await service.create(data) - const err = await service.create(data).catch((e) => e) + await service.createServiceZones(data) + const err = await service.createServiceZones(data).catch((e) => e) - expect(err).toBeDefined() - expect(err.constraint).toBe("IDX_fulfillment_set_name_unique") - }) + expect(err).toBeDefined() + expect(err.constraint).toBe("IDX_service_zone_name_unique") }) }) }) diff --git a/packages/fulfillment/src/services/fulfillment-module-service.ts b/packages/fulfillment/src/services/fulfillment-module-service.ts index d229ff3dd2fac..ae205d46423d5 100644 --- a/packages/fulfillment/src/services/fulfillment-module-service.ts +++ b/packages/fulfillment/src/services/fulfillment-module-service.ts @@ -386,9 +386,16 @@ export default class FulfillmentModuleService< sharedContext ) - return await this.baseRepository_.serialize(createdFulfillmentSets, { - populate: true, - }) + const serializedFulfillmentSets = await this.baseRepository_.serialize( + createdFulfillmentSets, + { + populate: true, + } + ) + + return Array.isArray(data) + ? serializedFulfillmentSets + : serializedFulfillmentSets[0] } createServiceZones( @@ -409,7 +416,92 @@ export default class FulfillmentModuleService< ): Promise< FulfillmentTypes.ServiceZoneDTO | FulfillmentTypes.ServiceZoneDTO[] > { - return [] + const data_ = Array.isArray(data) ? data : [data] + + const geoZonesToCreate: FulfillmentTypes.CreateGeoZoneDTO[] = [] + const serviceZoneGeoZonesMap = new Map< + string, + Map + >() + + /** + * The reasoning behind the following code that we want to deduplicate potential + * duplicated in order to be able to reuse the same one even though the + * user provides the same geo zone in multiple service zones. + */ + + data_.forEach((serviceZone) => { + if ("geo_zones" in serviceZone && serviceZone.geo_zones) { + const geo_zones = serviceZone.geo_zones + delete serviceZone.geo_zones + + const geoZoneTuple: [ + string, + FulfillmentTypes.CreateGeoZoneDTO | { id: string } + ][] = geo_zones.map((geoZone) => { + if (!("id" in geoZone)) { + geoZonesToCreate.push(geoZone) + } + + const geoZoneIdentifier = + FulfillmentModuleService.getGeoZoneIdentifier(geoZone) + + return [geoZoneIdentifier, geoZone] + }) + + serviceZoneGeoZonesMap.set(serviceZone.name, new Map(geoZoneTuple)) + } + }) + + if (geoZonesToCreate.length) { + // Deduplicate geo zones to create + const geoZoneToCreateMap = new Map( + geoZonesToCreate.map((geoZone) => [ + FulfillmentModuleService.getGeoZoneIdentifier(geoZone), + geoZone, + ]) + ) + + const createdGeoZones = await this.geoZoneService_.create( + [...geoZoneToCreateMap.values()], + sharedContext + ) + + for (const [serviceZoneName, geoZoneMap] of serviceZoneGeoZonesMap) { + for (const createdGeoZone of createdGeoZones) { + const geoZoneIdentifier = + FulfillmentModuleService.getGeoZoneIdentifier(createdGeoZone, { + preventIdUsage: true, + }) + + if (geoZoneMap.has(geoZoneIdentifier)) { + geoZoneMap.set(geoZoneIdentifier, createdGeoZone) + } + } + + for (const serviceZone of data_) { + if (serviceZone.name === serviceZoneName) { + serviceZone.geo_zones = [...geoZoneMap.values()] + } + } + } + } + + const createdServiceZones = await this.serviceZoneService_.create( + data_, + sharedContext + ) + + const serializedServiceZones = await this.baseRepository_.serialize( + createdServiceZones, + { + populate: true, + } + ) + + return Array.isArray(data) + ? serializedServiceZones + : serializedServiceZones[0] } createShippingOptions( From 7323cd23abbf7d36a6e3c34fa04b8180168d40bf Mon Sep 17 00:00:00 2001 From: adrien2p Date: Tue, 13 Feb 2024 12:00:42 +0100 Subject: [PATCH 14/29] Update fulfillment sets --- .../fulfillment-module-service.spec.ts | 396 ++++++++++++++++++ .../src/repositories/fulfillment-set.ts | 64 +-- .../services/fulfillment-module-service.ts | 269 ++++++++++-- 3 files changed, 678 insertions(+), 51 deletions(-) diff --git a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts index 5726eb3f65b4d..c7cce278f7ac2 100644 --- a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts +++ b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts @@ -414,4 +414,400 @@ describe("fulfillment module service", function () { expect(err.constraint).toBe("IDX_service_zone_name_unique") }) }) + + describe("on update", () => { + it("should update an existing fulfillment set", async function () { + const createData: CreateFulfillmentSetDTO = { + name: "test", + type: "test-type", + } + + const createdFulfillmentSet = await service.create(createData) + + const updateData = { + id: createdFulfillmentSet.id, + name: "updated-test", + type: "updated-test-type", + } + + const updatedFulfillmentSets = await service.update(updateData) + + expect(updatedFulfillmentSets).toEqual( + expect.objectContaining({ + id: createdFulfillmentSet.id, + name: updateData.name, + type: updateData.type, + }) + ) + }) + + it("should update a collection of fulfillment sets", async function () { + const createData = [ + { + name: "test", + type: "test-type", + }, + { + name: "test2", + type: "test-type2", + }, + ] + + const createdFulfillmentSets = await service.create(createData) + + const updateData = createdFulfillmentSets.map( + (fulfillmentSet, index) => ({ + id: fulfillmentSet.id, + name: `updated-test${index + 1}`, + type: `updated-test-type${index + 1}`, + }) + ) + + const updatedFulfillmentSets = await service.update(updateData) + + expect(updatedFulfillmentSets).toHaveLength(2) + + let i = 0 + for (const data_ of updateData) { + expect(updatedFulfillmentSets[i]).toEqual( + expect.objectContaining({ + id: createdFulfillmentSets[i].id, + name: data_.name, + type: data_.type, + }) + ) + ++i + } + }) + + it("should update an existing fulfillment set and replace old service zones by a new one", async function () { + const createData: CreateFulfillmentSetDTO = { + name: "test", + type: "test-type", + service_zones: [ + { + name: "service-zone-test", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + ], + }, + ], + } + + const createdFulfillmentSet = await service.create(createData) + + const createServiceZoneData = { + name: "service-zone-test2", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "us", + }, + ], + } + + const updateData = { + id: createdFulfillmentSet.id, + name: "updated-test", + type: "updated-test-type", + service_zones: [createServiceZoneData], + } + + const updatedFulfillmentSet = await service.update(updateData) + + expect(updatedFulfillmentSet).toEqual( + expect.objectContaining({ + id: updateData.id, + name: updateData.name, + type: updateData.type, + service_zones: expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + name: updateData.service_zones[0].name, + geo_zones: expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + type: updateData.service_zones[0].geo_zones[0].type, + country_code: + updateData.service_zones[0].geo_zones[0].country_code, + }), + ]), + }), + ]), + }) + ) + }) + + it("should update an existing fulfillment set and add a new service zone", async function () { + const createData: CreateFulfillmentSetDTO = { + name: "test", + type: "test-type", + service_zones: [ + { + name: "service-zone-test", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + ], + }, + ], + } + + const createdFulfillmentSet = await service.create(createData) + + const createServiceZoneData = { + name: "service-zone-test2", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "us", + }, + ], + } + + const updateData = { + id: createdFulfillmentSet.id, + name: "updated-test", + type: "updated-test-type", + service_zones: [ + { id: createdFulfillmentSet.service_zones[0].id }, + createServiceZoneData, + ], + } + + const updatedFulfillmentSet = await service.update(updateData) + + expect(updatedFulfillmentSet).toEqual( + expect.objectContaining({ + id: updateData.id, + name: updateData.name, + type: updateData.type, + service_zones: expect.arrayContaining([ + expect.objectContaining({ + id: createdFulfillmentSet.service_zones[0].id, + }), + expect.objectContaining({ + id: expect.any(String), + name: updateData.service_zones[1].name, + geo_zones: expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + type: updateData.service_zones[1].geo_zones[0].type, + country_code: + updateData.service_zones[1].geo_zones[0].country_code, + }), + ]), + }), + ]), + }) + ) + }) + + it("should fail on duplicated fulfillment set name", async function () { + const createData = [ + { + name: "test", + type: "test-type", + }, + { + name: "test2", + type: "test-type2", + }, + ] + + const createdFulfillmentSets = await service.create(createData) + + const updateData = { + id: createdFulfillmentSets[1].id, + name: "test", // This is the name of the first fulfillment set + type: "updated-test-type2", + } + + const err = await service.update(updateData).catch((e) => e) + + expect(err).toBeDefined() + expect(err.constraint).toBe("IDX_fulfillment_set_name_unique") + }) + + it("should update a collection of fulfillment sets and replace old service zones by new ones", async function () { + const createData: CreateFulfillmentSetDTO[] = [ + { + name: "test1", + type: "test-type1", + service_zones: [ + { + name: "service-zone-test1", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + ], + }, + ], + }, + { + name: "test2", + type: "test-type2", + service_zones: [ + { + name: "service-zone-test2", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "us", + }, + ], + }, + ], + }, + ] + + const createdFulfillmentSets = await service.create(createData) + + const updateData = createdFulfillmentSets.map( + (fulfillmentSet, index) => ({ + id: fulfillmentSet.id, + name: `updated-test${index + 1}`, + type: `updated-test-type${index + 1}`, + service_zones: [ + { + name: `new-service-zone-test`, + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: index % 2 === 0 ? "us" : "fr", + }, + ], + }, + ], + }) + ) + + const updatedFulfillmentSets = await service.update(updateData) + + expect(updatedFulfillmentSets).toHaveLength(2) + + let i = 0 + for (const data_ of updateData) { + expect(updatedFulfillmentSets[i]).toEqual( + expect.objectContaining({ + id: data_.id, + name: data_.name, + type: data_.type, + service_zones: expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + name: data_.service_zones[0].name, + geo_zones: expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + type: data_.service_zones[0].geo_zones[0].type, + country_code: + data_.service_zones[0].geo_zones[0].country_code, + }), + ]), + }), + ]), + }) + ) + ++i + } + }) + + it("should update a collection of fulfillment sets and add new service zones", async function () { + const createData: CreateFulfillmentSetDTO[] = [ + { + name: "test1", + type: "test-type1", + service_zones: [ + { + name: "service-zone-test1", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + ], + }, + ], + }, + { + name: "test2", + type: "test-type2", + service_zones: [ + { + name: "service-zone-test2", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "us", + }, + ], + }, + ], + }, + ] + + const createdFulfillmentSets = await service.create(createData) + + const updateData = createdFulfillmentSets.map( + (fulfillmentSet, index) => ({ + id: fulfillmentSet.id, + name: `updated-test${index + 1}`, + type: `updated-test-type${index + 1}`, + service_zones: [ + ...fulfillmentSet.service_zones, + { + name: `added-service-zone-test`, + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: index % 2 === 0 ? "us" : "fr", + }, + ], + }, + ], + }) + ) + + const updatedFulfillmentSets = await service.update(updateData) + + expect(updatedFulfillmentSets).toHaveLength(2) + + let i = 0 + for (const data_ of updateData) { + expect(updatedFulfillmentSets[i]).toEqual( + expect.objectContaining({ + id: data_.id, + name: data_.name, + type: data_.type, + service_zones: expect.arrayContaining([ + expect.objectContaining({ + id: createdFulfillmentSets[i].service_zones[0].id, + }), + expect.objectContaining({ + id: expect.any(String), + name: data_.service_zones[1].name, + geo_zones: expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + type: data_.service_zones[1].geo_zones[0].type, + country_code: + data_.service_zones[1].geo_zones[0].country_code, + }), + ]), + }), + ]), + }) + ) + ++i + } + }) + }) }) diff --git a/packages/fulfillment/src/repositories/fulfillment-set.ts b/packages/fulfillment/src/repositories/fulfillment-set.ts index f32c544b228bc..e45c989becbeb 100644 --- a/packages/fulfillment/src/repositories/fulfillment-set.ts +++ b/packages/fulfillment/src/repositories/fulfillment-set.ts @@ -1,8 +1,7 @@ -/* import { Context, FulfillmentTypes } from "@medusajs/types" import { DALUtils, promiseAll } from "@medusajs/utils" -import { SqlEntityManager } from "@mikro-orm/postgresql" import { FulfillmentSet, ServiceZone } from "@models" +import { SqlEntityManager } from "@mikro-orm/postgresql" interface CreateFulfillmentSetDTO extends FulfillmentTypes.CreateFulfillmentSetDTO { @@ -12,34 +11,47 @@ interface CreateFulfillmentSetDTO export class FulfillmentSetRepository extends DALUtils.mikroOrmBaseRepositoryFactory( FulfillmentSet ) { - async create( - data: CreateFulfillmentSetDTO[], - context: Context = {} + async update( + data: { + entity: FulfillmentSet + update: FulfillmentTypes.FulfillmentSetDTO + }[], + context?: Context ): Promise { const manager = this.getActiveManager(context) - return await promiseAll( - data.map(async (fulfillmentSetData) => { - const { service_zones, ...fulfillmentSetDataOnly } = fulfillmentSetData - const fulfillmentSet = manager.create( - FulfillmentSet, - fulfillmentSetDataOnly - ) - - console.log(JSON.stringify(service_zones, null, 2)) - if (service_zones?.length) { - console.log(JSON.stringify(fulfillmentSet.service_zones, null, 2)) - fulfillmentSet.service_zones.add( - service_zones.map((serviceZone) => - manager.create(ServiceZone, serviceZone) - ) - ) - } - - manager.persist(fulfillmentSet) - return fulfillmentSet + // init all service zones collections + await promiseAll( + data.map(async ({ entity }) => { + return await entity.service_zones.init() }) ) + + const flfillmentSetsToUpdate = data.map(({ entity, update }) => { + const { service_zones, ...restToUpdate } = update + + const currentServiceZones = entity.service_zones.getItems() + const serviceZonesToDetach = currentServiceZones.filter( + (serviceZone) => + !update.service_zones.find( + (newServiceZone) => newServiceZone.id === serviceZone.id + ) + ) + const serviceZonesToAttach = update.service_zones.filter( + (newServiceZone) => + !currentServiceZones.find( + (serviceZone) => serviceZone.id === newServiceZone.id + ) + ) + + entity.service_zones.remove(serviceZonesToDetach) + entity.service_zones.add(serviceZonesToAttach as unknown as ServiceZone[]) + + return manager.assign(entity, restToUpdate) + }) + + manager.persist(flfillmentSetsToUpdate) + + return flfillmentSetsToUpdate } } -*/ diff --git a/packages/fulfillment/src/services/fulfillment-module-service.ts b/packages/fulfillment/src/services/fulfillment-module-service.ts index ae205d46423d5..438716614220b 100644 --- a/packages/fulfillment/src/services/fulfillment-module-service.ts +++ b/packages/fulfillment/src/services/fulfillment-module-service.ts @@ -9,6 +9,7 @@ import { UpdateFulfillmentSetDTO, } from "@medusajs/types" import { + InjectManager, InjectTransactionManager, ModulesSdkUtils, promiseAll, @@ -115,7 +116,10 @@ export default class FulfillmentModuleService< * @protected */ protected async prepareCreateData( - data: FulfillmentTypes.CreateFulfillmentSetDTO[], + data: ( + | FulfillmentTypes.CreateFulfillmentSetDTO + | FulfillmentTypes.UpdateFulfillmentSetDTO + )[], sharedContext?: Context ): Promise<{ existingServiceZones: TServiceZoneEntity[] @@ -213,7 +217,7 @@ export default class FulfillmentModuleService< sharedContext?: Context ): Promise - @InjectTransactionManager("baseRepository_") + @InjectManager("baseRepository_") async create( data: | FulfillmentTypes.CreateFulfillmentSetDTO @@ -222,6 +226,22 @@ export default class FulfillmentModuleService< ): Promise< FulfillmentTypes.FulfillmentSetDTO | FulfillmentTypes.FulfillmentSetDTO[] > { + const createdFulfillmentSets = await this.create_(data, sharedContext) + + return await this.baseRepository_.serialize< + FulfillmentTypes.FulfillmentSetDTO | FulfillmentTypes.FulfillmentSetDTO[] + >(createdFulfillmentSets, { + populate: true, + }) + } + + @InjectTransactionManager("baseRepository_") + async create_( + data: + | FulfillmentTypes.CreateFulfillmentSetDTO + | FulfillmentTypes.CreateFulfillmentSetDTO[], + sharedContext?: Context + ): Promise { const data_ = Array.isArray(data) ? data : [data] const fulfillmentSetMap = new Map< @@ -386,16 +406,9 @@ export default class FulfillmentModuleService< sharedContext ) - const serializedFulfillmentSets = await this.baseRepository_.serialize( - createdFulfillmentSets, - { - populate: true, - } - ) - return Array.isArray(data) - ? serializedFulfillmentSets - : serializedFulfillmentSets[0] + ? createdFulfillmentSets + : createdFulfillmentSets[0] } createServiceZones( @@ -407,7 +420,7 @@ export default class FulfillmentModuleService< sharedContext?: Context ): Promise - @InjectTransactionManager("baseRepository_") + @InjectManager("baseRepository_") async createServiceZones( data: | FulfillmentTypes.CreateServiceZoneDTO[] @@ -416,6 +429,25 @@ export default class FulfillmentModuleService< ): Promise< FulfillmentTypes.ServiceZoneDTO | FulfillmentTypes.ServiceZoneDTO[] > { + const createdServiceZones = await this.createServiceZones_( + data, + sharedContext + ) + + return await this.baseRepository_.serialize< + FulfillmentTypes.ServiceZoneDTO | FulfillmentTypes.ServiceZoneDTO[] + >(createdServiceZones, { + populate: true, + }) + } + + @InjectTransactionManager("baseRepository_") + async createServiceZones_( + data: + | FulfillmentTypes.CreateServiceZoneDTO[] + | FulfillmentTypes.CreateServiceZoneDTO, + sharedContext?: Context + ): Promise { const data_ = Array.isArray(data) ? data : [data] const geoZonesToCreate: FulfillmentTypes.CreateGeoZoneDTO[] = [] @@ -491,17 +523,7 @@ export default class FulfillmentModuleService< data_, sharedContext ) - - const serializedServiceZones = await this.baseRepository_.serialize( - createdServiceZones, - { - populate: true, - } - ) - - return Array.isArray(data) - ? serializedServiceZones - : serializedServiceZones[0] + return Array.isArray(data) ? createdServiceZones : createdServiceZones[0] } createShippingOptions( @@ -534,14 +556,211 @@ export default class FulfillmentModuleService< sharedContext?: Context ): Promise - @InjectTransactionManager("baseRepository_") + @InjectManager("baseRepository_") async update( data: UpdateFulfillmentSetDTO[] | UpdateFulfillmentSetDTO, sharedContext?: Context ): Promise< FulfillmentTypes.FulfillmentSetDTO[] | FulfillmentTypes.FulfillmentSetDTO > { - return [] + const updatedFulfillmentSets = await this.update_(data, sharedContext) + + return await this.baseRepository_.serialize< + FulfillmentTypes.FulfillmentSetDTO | FulfillmentTypes.FulfillmentSetDTO[] + >(updatedFulfillmentSets, { + populate: true, + }) + } + + /** + * Update fulfillment sets. This method is responsible for updating the fulfillment + * sets and the service zones that are attached to the fulfillment. The geo zones are + * discarded here because they are not directly attached to the fulfillment set. + * Instead, the user can create and update the geo zones through the service zone + * or create a new service zone to be attached to the fulfillment set. + * + * @param data + * @param sharedContext + */ + @InjectTransactionManager("baseRepository_") + async update_( + data: UpdateFulfillmentSetDTO[] | UpdateFulfillmentSetDTO, + sharedContext?: Context + ): Promise { + const data_ = Array.isArray(data) ? data : [data] + + const fulfillmentSetMap = new Map< + string, + FulfillmentTypes.UpdateFulfillmentSetDTO + >() + + const fulfillmentSetServiceZonesMap = new Map< + string, + Map< + string, + Required["service_zones"][number] + > + >() + + const serviceZoneGeoZonesMap = new Map< + string, + Map< + string, + | FulfillmentTypes.CreateGeoZoneDTO + | FulfillmentTypes.UpdateGeoZoneDTO + | { id: string } + > + >() + + const serviceZonesToCreate: ( + | FulfillmentTypes.UpdateServiceZoneDTO + | FulfillmentTypes.CreateServiceZoneDTO + )[] = [] + const geoZonesToCreate: ( + | FulfillmentTypes.UpdateGeoZoneDTO + | FulfillmentTypes.CreateGeoZoneDTO + )[] = [] + + const { + existingServiceZones, + existingServiceZonesMap, + existingGeoZones, + existingGeoZonesMap, + } = await this.prepareCreateData(data_, sharedContext) + + data_.forEach(({ service_zones, ...fulfillmentSetDataOnly }) => { + fulfillmentSetMap.set(fulfillmentSetDataOnly.id, fulfillmentSetDataOnly) + + if (service_zones?.length) { + const serviceZoneTuple: [ + string, + Required["service_zones"][number] + ][] = service_zones.map((serviceZone) => { + let geoZoneTuple: [ + string, + ( + | FulfillmentTypes.CreateGeoZoneDTO + | FulfillmentTypes.UpdateGeoZoneDTO + | { id: string } + ) + ][] = [] + + if ("geo_zones" in serviceZone && serviceZone.geo_zones) { + const geo_zones = serviceZone.geo_zones + delete serviceZone.geo_zones + + geoZoneTuple = geo_zones.map((geoZone) => { + let existingGeoZone = + "id" in geoZone ? existingGeoZonesMap.get(geoZone.id)! : null + + if (!("id" in geoZone)) { + geoZonesToCreate.push(geoZone) + } + + const geoZoneIdentifier = + FulfillmentModuleService.getGeoZoneIdentifier(geoZone) + + return [geoZoneIdentifier, existingGeoZone ?? geoZone] + }) + } + + let existingZone = + "id" in serviceZone + ? existingServiceZonesMap.get(serviceZone.id)! + : null + + if (!("id" in serviceZone)) { + serviceZonesToCreate.push(serviceZone) + } + + const serviceZoneIdentifier = + "id" in serviceZone ? serviceZone.id : serviceZone.name + + serviceZoneGeoZonesMap.set( + serviceZoneIdentifier, + new Map(geoZoneTuple) + ) + + return [serviceZoneIdentifier, existingZone ?? serviceZone] + }) + + fulfillmentSetServiceZonesMap.set( + fulfillmentSetDataOnly.id, + new Map(serviceZoneTuple) + ) + } + }) + + if (geoZonesToCreate.length) { + const geoZoneToUpdateMap = new Map( + geoZonesToCreate.map((geoZone) => [ + FulfillmentModuleService.getGeoZoneIdentifier(geoZone), + geoZone, + ]) + ) + + const createdGeoZones = await this.geoZoneService_.create( + [...geoZoneToUpdateMap.values()], + sharedContext + ) + + for (const [serviceZoneName, geoZoneMap] of serviceZoneGeoZonesMap) { + for (const createdGeoZone of createdGeoZones) { + const geoZoneIdentifier = + FulfillmentModuleService.getGeoZoneIdentifier(createdGeoZone, { + preventIdUsage: true, + }) + + if (geoZoneMap.has(geoZoneIdentifier)) { + geoZoneMap.set(geoZoneIdentifier, createdGeoZone) + } + } + + for (const serviceZone of serviceZonesToCreate) { + if (serviceZone.name === serviceZoneName) { + serviceZone.geo_zones = [...geoZoneMap.values()] + } + } + } + } + + if (serviceZonesToCreate.length) { + const serviceZoneToUpdateMap = new Map( + serviceZonesToCreate.map((serviceZone) => [ + serviceZone.name, + serviceZone, + ]) + ) + + const createdServiceZones = await this.serviceZoneService_.create( + [...serviceZoneToUpdateMap.values()], + sharedContext + ) + + for (const [ + fulfillmentSetName, + serviceZoneMap, + ] of fulfillmentSetServiceZonesMap) { + for (const updatedServiceZone of createdServiceZones) { + if (serviceZoneMap.has(updatedServiceZone.name)) { + serviceZoneMap.set(updatedServiceZone.name, updatedServiceZone) + } + } + + const fulfillmentSet = fulfillmentSetMap.get(fulfillmentSetName)! + fulfillmentSet.service_zones = [...serviceZoneMap.values()] + fulfillmentSetMap.set(fulfillmentSetName, fulfillmentSet) + } + } + + const updatedFulfillmentSets = await this.fulfillmentSetService_.update( + [...fulfillmentSetMap.values()], + sharedContext + ) + + return Array.isArray(data) + ? updatedFulfillmentSets + : updatedFulfillmentSets[0] } updateServiceZones( From e1ba6cf29151562d46c4ab932fdf07b3ab86abc2 Mon Sep 17 00:00:00 2001 From: adrien2p Date: Tue, 13 Feb 2024 12:03:16 +0100 Subject: [PATCH 15/29] cleanup --- .../services/fulfillment-module-service.ts | 23 ++++--------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/packages/fulfillment/src/services/fulfillment-module-service.ts b/packages/fulfillment/src/services/fulfillment-module-service.ts index 438716614220b..b49747a71a26e 100644 --- a/packages/fulfillment/src/services/fulfillment-module-service.ts +++ b/packages/fulfillment/src/services/fulfillment-module-service.ts @@ -604,22 +604,11 @@ export default class FulfillmentModuleService< const serviceZoneGeoZonesMap = new Map< string, - Map< - string, - | FulfillmentTypes.CreateGeoZoneDTO - | FulfillmentTypes.UpdateGeoZoneDTO - | { id: string } - > + Map >() - const serviceZonesToCreate: ( - | FulfillmentTypes.UpdateServiceZoneDTO - | FulfillmentTypes.CreateServiceZoneDTO - )[] = [] - const geoZonesToCreate: ( - | FulfillmentTypes.UpdateGeoZoneDTO - | FulfillmentTypes.CreateGeoZoneDTO - )[] = [] + const serviceZonesToCreate: FulfillmentTypes.CreateServiceZoneDTO[] = [] + const geoZonesToCreate: FulfillmentTypes.CreateGeoZoneDTO[] = [] const { existingServiceZones, @@ -638,11 +627,7 @@ export default class FulfillmentModuleService< ][] = service_zones.map((serviceZone) => { let geoZoneTuple: [ string, - ( - | FulfillmentTypes.CreateGeoZoneDTO - | FulfillmentTypes.UpdateGeoZoneDTO - | { id: string } - ) + FulfillmentTypes.CreateGeoZoneDTO | { id: string } ][] = [] if ("geo_zones" in serviceZone && serviceZone.geo_zones) { From 5a1f051c3fa1ed37b2a9b82492747f904a72f86b Mon Sep 17 00:00:00 2001 From: adrien2p Date: Tue, 13 Feb 2024 12:26:03 +0100 Subject: [PATCH 16/29] fix integration --- .../__tests__/fulfillment-module-service.spec.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts index c7cce278f7ac2..745e9b38512b3 100644 --- a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts +++ b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts @@ -40,10 +40,9 @@ describe("fulfillment module service", function () { type: "test-type", } - const fulfillmentSets = await service.create(data) + const fulfillmentSet = await service.create(data) - expect(fulfillmentSets).toHaveLength(1) - expect(fulfillmentSets[0]).toEqual( + expect(fulfillmentSet).toEqual( expect.objectContaining({ id: expect.any(String), name: data.name, @@ -92,10 +91,9 @@ describe("fulfillment module service", function () { ], } - const fulfillmentSets = await service.create(data) + const fulfillmentSet = await service.create(data) - expect(fulfillmentSets).toHaveLength(1) - expect(fulfillmentSets[0]).toEqual( + expect(fulfillmentSet).toEqual( expect.objectContaining({ id: expect.any(String), name: data.name, @@ -186,10 +184,9 @@ describe("fulfillment module service", function () { ], } - const fulfillmentSets = await service.create(data) + const fulfillmentSet = await service.create(data) - expect(fulfillmentSets).toHaveLength(1) - expect(fulfillmentSets[0]).toEqual( + expect(fulfillmentSet).toEqual( expect.objectContaining({ id: expect.any(String), name: data.name, From 6beae8b5b869e04fd6233875c08d3fe246a60849 Mon Sep 17 00:00:00 2001 From: adrien2p Date: Tue, 13 Feb 2024 14:26:54 +0100 Subject: [PATCH 17/29] update service zones --- .../fulfillment-module-service.spec.ts | 149 +++++++++++- .../src/repositories/fulfillment-set.ts | 2 + .../src/repositories/service-zone.ts | 0 .../services/fulfillment-module-service.ts | 227 ++++++++++++++---- 4 files changed, 330 insertions(+), 48 deletions(-) create mode 100644 packages/fulfillment/src/repositories/service-zone.ts diff --git a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts index 745e9b38512b3..5f1210393ba8c 100644 --- a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts +++ b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts @@ -678,7 +678,7 @@ describe("fulfillment module service", function () { geo_zones: [ { type: GeoZoneType.COUNTRY, - country_code: index % 2 === 0 ? "us" : "fr", + country_code: "test", }, ], }, @@ -765,7 +765,7 @@ describe("fulfillment module service", function () { geo_zones: [ { type: GeoZoneType.COUNTRY, - country_code: index % 2 === 0 ? "us" : "fr", + country_code: "test", }, ], }, @@ -807,4 +807,149 @@ describe("fulfillment module service", function () { } }) }) + + describe("on update service zones", () => { + it("should update an existing service zone", async function () { + const createData = { + name: "service-zone-test", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + ], + } + + const createdServiceZone = await service.createServiceZones(createData) + + const updateData = { + id: createdServiceZone.id, + name: "updated-service-zone-test", + geo_zones: [ + { + id: createdServiceZone.geo_zones[0].id, + type: GeoZoneType.COUNTRY, + country_code: "us", + }, + ], + } + + const updatedServiceZone = await service.updateServiceZones(updateData) + + expect(updatedServiceZone).toEqual( + expect.objectContaining({ + id: updateData.id, + name: updateData.name, + geo_zones: expect.arrayContaining([ + expect.objectContaining({ + id: updateData.geo_zones[0].id, + type: updateData.geo_zones[0].type, + country_code: updateData.geo_zones[0].country_code, + }), + ]), + }) + ) + }) + + it("should update a collection of service zones", async function () { + const createData = [ + { + name: "service-zone-test", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + ], + }, + { + name: "service-zone-test2", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "us", + }, + ], + }, + ] + + const createdServiceZones = await service.createServiceZones(createData) + + const updateData = createdServiceZones.map((serviceZone, index) => ({ + id: serviceZone.id, + name: `updated-service-zone-test${index + 1}`, + geo_zones: [ + { + id: serviceZone.geo_zones[0].id, + type: GeoZoneType.COUNTRY, + country_code: index % 2 === 0 ? "us" : "fr", + }, + ], + })) + + const updatedServiceZones = await service.updateServiceZones(updateData) + + expect(updatedServiceZones).toHaveLength(2) + + let i = 0 + for (const data_ of updateData) { + expect(updatedServiceZones[i]).toEqual( + expect.objectContaining({ + id: data_.id, + name: data_.name, + geo_zones: expect.arrayContaining([ + expect.objectContaining({ + id: data_.geo_zones[0].id, + type: data_.geo_zones[0].type, + country_code: data_.geo_zones[0].country_code, + }), + ]), + }) + ) + ++i + } + }) + + it("should fail on duplicated service zone name", async function () { + const createData = [ + { + name: "service-zone-test", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + ], + }, + { + name: "service-zone-test2", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "us", + }, + ], + }, + ] + + const createdServiceZones = await service.createServiceZones(createData) + + const updateData = { + id: createdServiceZones[1].id, + name: "service-zone-test", // This is the name of the first service zone + geo_zones: [ + { + id: createdServiceZones[1].geo_zones[0].id, + type: GeoZoneType.COUNTRY, + country_code: "us", + }, + ], + } + + const err = await service.updateServiceZones(updateData).catch((e) => e) + + expect(err).toBeDefined() + expect(err.constraint).toBe("IDX_service_zone_name_unique") + }) + }) }) diff --git a/packages/fulfillment/src/repositories/fulfillment-set.ts b/packages/fulfillment/src/repositories/fulfillment-set.ts index e45c989becbeb..97b718201f64a 100644 --- a/packages/fulfillment/src/repositories/fulfillment-set.ts +++ b/packages/fulfillment/src/repositories/fulfillment-set.ts @@ -1,3 +1,4 @@ +/* import { Context, FulfillmentTypes } from "@medusajs/types" import { DALUtils, promiseAll } from "@medusajs/utils" import { FulfillmentSet, ServiceZone } from "@models" @@ -55,3 +56,4 @@ export class FulfillmentSetRepository extends DALUtils.mikroOrmBaseRepositoryFac return flfillmentSetsToUpdate } } +*/ diff --git a/packages/fulfillment/src/repositories/service-zone.ts b/packages/fulfillment/src/repositories/service-zone.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/fulfillment/src/services/fulfillment-module-service.ts b/packages/fulfillment/src/services/fulfillment-module-service.ts index b49747a71a26e..f5f1b76fc02bc 100644 --- a/packages/fulfillment/src/services/fulfillment-module-service.ts +++ b/packages/fulfillment/src/services/fulfillment-module-service.ts @@ -236,7 +236,7 @@ export default class FulfillmentModuleService< } @InjectTransactionManager("baseRepository_") - async create_( + protected async create_( data: | FulfillmentTypes.CreateFulfillmentSetDTO | FulfillmentTypes.CreateFulfillmentSetDTO[], @@ -298,8 +298,8 @@ export default class FulfillmentModuleService< delete serviceZone.geo_zones geoZoneTuple = geo_zones.map((geoZone) => { - let existingGeoZone = - "id" in geoZone ? existingGeoZonesMap.get(geoZone.id)! : null + const existingGeoZone = + "id" in geoZone ? existingGeoZonesMap.get(geoZone.id)! : geoZone if (!("id" in geoZone)) { geoZonesToCreate.push(geoZone) @@ -308,14 +308,14 @@ export default class FulfillmentModuleService< const geoZoneIdentifier = FulfillmentModuleService.getGeoZoneIdentifier(geoZone) - return [geoZoneIdentifier, existingGeoZone ?? geoZone] + return [geoZoneIdentifier, existingGeoZone] }) } - let existingZone = + const existingZone = "id" in serviceZone ? existingServiceZonesMap.get(serviceZone.id)! - : null + : serviceZone if (!("id" in serviceZone)) { serviceZonesToCreate.push(serviceZone) @@ -329,7 +329,7 @@ export default class FulfillmentModuleService< new Map(geoZoneTuple) ) - return [serviceZoneIdentifier, existingZone ?? serviceZone] + return [serviceZoneIdentifier, existingZone] }) fulfillmentSetServiceZonesMap.set( @@ -352,7 +352,7 @@ export default class FulfillmentModuleService< sharedContext ) - for (const [serviceZoneName, geoZoneMap] of serviceZoneGeoZonesMap) { + for (const [, geoZoneMap] of serviceZoneGeoZonesMap) { for (const createdGeoZone of createdGeoZones) { const geoZoneIdentifier = FulfillmentModuleService.getGeoZoneIdentifier(createdGeoZone, { @@ -363,12 +363,14 @@ export default class FulfillmentModuleService< geoZoneMap.set(geoZoneIdentifier, createdGeoZone) } } + } + } - for (const serviceZone of serviceZonesToCreate) { - if (serviceZone.name === serviceZoneName) { - serviceZone.geo_zones = [...geoZoneMap.values()] - } - } + // re assign the geo zones to the service zones + for (const serviceZone of serviceZonesToCreate) { + if (serviceZoneGeoZonesMap.has(serviceZone.name)) { + const geoZones = serviceZoneGeoZonesMap.get(serviceZone.name)!.values() + serviceZone.geo_zones = [...geoZones] } } @@ -385,19 +387,22 @@ export default class FulfillmentModuleService< sharedContext ) - for (const [ - fulfillmentSetName, - serviceZoneMap, - ] of fulfillmentSetServiceZonesMap) { + for (const [, serviceZoneMap] of fulfillmentSetServiceZonesMap) { for (const createdServiceZone of createdServiceZones) { if (serviceZoneMap.has(createdServiceZone.name)) { serviceZoneMap.set(createdServiceZone.name, createdServiceZone) } } + } + } - const fulfillmentSet = fulfillmentSetMap.get(fulfillmentSetName)! - fulfillmentSet.service_zones = [...serviceZoneMap.values()] - fulfillmentSetMap.set(fulfillmentSetName, fulfillmentSet) + // re assign the service zones to the fulfillment sets + for (const fulfillmentSet of fulfillmentSetMap.values()) { + if (fulfillmentSetServiceZonesMap.has(fulfillmentSet.name)) { + const serviceZones = fulfillmentSetServiceZonesMap + .get(fulfillmentSet.name)! + .values() + fulfillmentSet.service_zones = [...serviceZones] } } @@ -442,7 +447,7 @@ export default class FulfillmentModuleService< } @InjectTransactionManager("baseRepository_") - async createServiceZones_( + protected async createServiceZones_( data: | FulfillmentTypes.CreateServiceZoneDTO[] | FulfillmentTypes.CreateServiceZoneDTO, @@ -510,12 +515,14 @@ export default class FulfillmentModuleService< geoZoneMap.set(geoZoneIdentifier, createdGeoZone) } } + } + } - for (const serviceZone of data_) { - if (serviceZone.name === serviceZoneName) { - serviceZone.geo_zones = [...geoZoneMap.values()] - } - } + // re assign the geo zones to the service zones + for (const serviceZone of data_) { + if (serviceZoneGeoZonesMap.has(serviceZone.name)) { + const geoZones = serviceZoneGeoZonesMap.get(serviceZone.name)!.values() + serviceZone.geo_zones = [...geoZones] } } @@ -583,7 +590,7 @@ export default class FulfillmentModuleService< * @param sharedContext */ @InjectTransactionManager("baseRepository_") - async update_( + protected async update_( data: UpdateFulfillmentSetDTO[] | UpdateFulfillmentSetDTO, sharedContext?: Context ): Promise { @@ -635,8 +642,8 @@ export default class FulfillmentModuleService< delete serviceZone.geo_zones geoZoneTuple = geo_zones.map((geoZone) => { - let existingGeoZone = - "id" in geoZone ? existingGeoZonesMap.get(geoZone.id)! : null + const existingGeoZone = + "id" in geoZone ? existingGeoZonesMap.get(geoZone.id)! : geoZone if (!("id" in geoZone)) { geoZonesToCreate.push(geoZone) @@ -645,11 +652,11 @@ export default class FulfillmentModuleService< const geoZoneIdentifier = FulfillmentModuleService.getGeoZoneIdentifier(geoZone) - return [geoZoneIdentifier, existingGeoZone ?? geoZone] + return [geoZoneIdentifier, existingGeoZone] }) } - let existingZone = + const existingZone = "id" in serviceZone ? existingServiceZonesMap.get(serviceZone.id)! : null @@ -689,7 +696,7 @@ export default class FulfillmentModuleService< sharedContext ) - for (const [serviceZoneName, geoZoneMap] of serviceZoneGeoZonesMap) { + for (const [, geoZoneMap] of serviceZoneGeoZonesMap) { for (const createdGeoZone of createdGeoZones) { const geoZoneIdentifier = FulfillmentModuleService.getGeoZoneIdentifier(createdGeoZone, { @@ -700,12 +707,14 @@ export default class FulfillmentModuleService< geoZoneMap.set(geoZoneIdentifier, createdGeoZone) } } + } + } - for (const serviceZone of serviceZonesToCreate) { - if (serviceZone.name === serviceZoneName) { - serviceZone.geo_zones = [...geoZoneMap.values()] - } - } + // re assign the geo zones to the service zones + for (const serviceZone of serviceZonesToCreate) { + if (serviceZoneGeoZonesMap.has(serviceZone.name)) { + const geoZones = serviceZoneGeoZonesMap.get(serviceZone.name)!.values() + serviceZone.geo_zones = [...geoZones] } } @@ -722,19 +731,22 @@ export default class FulfillmentModuleService< sharedContext ) - for (const [ - fulfillmentSetName, - serviceZoneMap, - ] of fulfillmentSetServiceZonesMap) { + for (const [, serviceZoneMap] of fulfillmentSetServiceZonesMap) { for (const updatedServiceZone of createdServiceZones) { if (serviceZoneMap.has(updatedServiceZone.name)) { serviceZoneMap.set(updatedServiceZone.name, updatedServiceZone) } } + } + } - const fulfillmentSet = fulfillmentSetMap.get(fulfillmentSetName)! - fulfillmentSet.service_zones = [...serviceZoneMap.values()] - fulfillmentSetMap.set(fulfillmentSetName, fulfillmentSet) + // re assign the service zones to the fulfillment sets + for (const fulfillmentSet of fulfillmentSetMap.values()) { + if (fulfillmentSetServiceZonesMap.has(fulfillmentSet.id)) { + const serviceZones = fulfillmentSetServiceZonesMap + .get(fulfillmentSet.id)! + .values() + fulfillmentSet.service_zones = [...serviceZones] } } @@ -757,7 +769,7 @@ export default class FulfillmentModuleService< sharedContext?: Context ): Promise - @InjectTransactionManager("baseRepository_") + @InjectManager("baseRepository_") async updateServiceZones( data: | FulfillmentTypes.UpdateServiceZoneDTO[] @@ -766,7 +778,130 @@ export default class FulfillmentModuleService< ): Promise< FulfillmentTypes.ServiceZoneDTO[] | FulfillmentTypes.ServiceZoneDTO > { - return [] + const updatedServiceZones = await this.updateServiceZones_( + data, + sharedContext + ) + + return await this.baseRepository_.serialize< + FulfillmentTypes.ServiceZoneDTO | FulfillmentTypes.ServiceZoneDTO[] + >(updatedServiceZones, { + populate: true, + }) + } + + protected async updateServiceZones_( + data: + | FulfillmentTypes.UpdateServiceZoneDTO[] + | FulfillmentTypes.UpdateServiceZoneDTO, + sharedContext?: Context + ): Promise { + const data_ = Array.isArray(data) ? data : [data] + + const geoZonesToCreate: FulfillmentTypes.CreateGeoZoneDTO[] = [] + const serviceZoneGeoZonesMap = new Map< + string, + Map + >() + + const existingGeoZones: TGeoZoneEntity[] = [] + let existingGeoZonesMap = new Map() + + const geoZoneIds: string[] = [] + + data_.forEach((serviceZone) => { + if ("geo_zones" in serviceZone && serviceZone.geo_zones) { + const geo_zones = serviceZone.geo_zones + + geo_zones.forEach((geoZone) => { + if ("id" in geoZone) { + geoZoneIds.push(geoZone.id) + } + }) + } + }) + + if (geoZoneIds.length) { + existingGeoZones.push( + ...(await this.geoZoneService_.list( + { + id: geoZoneIds, + }, + {}, + sharedContext + )) + ) + existingGeoZonesMap = new Map( + existingGeoZones.map((geoZone) => [geoZone.id, geoZone]) + ) + } + + data_.forEach((serviceZone) => { + if ("geo_zones" in serviceZone && serviceZone.geo_zones) { + const geo_zones = serviceZone.geo_zones + + const geoZoneTuple: [ + string, + FulfillmentTypes.CreateGeoZoneDTO | { id: string } + ][] = geo_zones.map((geoZone) => { + if (!("id" in geoZone)) { + geoZonesToCreate.push(geoZone) + } + + const existingZone = + "id" in geoZone ? existingGeoZonesMap.get(geoZone.id)! : geoZone + + const geoZoneIdentifier = + FulfillmentModuleService.getGeoZoneIdentifier(geoZone) + + return [geoZoneIdentifier, existingZone] + }) + + serviceZoneGeoZonesMap.set(serviceZone.id, new Map(geoZoneTuple)) + } + }) + + if (geoZonesToCreate.length) { + const geoZoneToUpdateMap = new Map( + geoZonesToCreate.map((geoZone) => [ + FulfillmentModuleService.getGeoZoneIdentifier(geoZone), + geoZone, + ]) + ) + + const createdGeoZones = await this.geoZoneService_.create( + [...geoZoneToUpdateMap.values()], + sharedContext + ) + + for (const [serviceZoneId, geoZoneMap] of serviceZoneGeoZonesMap) { + for (const createdGeoZone of createdGeoZones) { + const geoZoneIdentifier = + FulfillmentModuleService.getGeoZoneIdentifier(createdGeoZone, { + preventIdUsage: true, + }) + + if (geoZoneMap.has(geoZoneIdentifier)) { + geoZoneMap.set(geoZoneIdentifier, createdGeoZone) + } + } + } + } + + // re assign the geo zones to the service zones + for (const serviceZone of data_) { + if (serviceZoneGeoZonesMap.has(serviceZone.id)) { + const geoZones = serviceZoneGeoZonesMap.get(serviceZone.id)!.values() + serviceZone.geo_zones = [...geoZones] + } + } + + const updatedServiceZones = await this.serviceZoneService_.update( + data_, + sharedContext + ) + + return Array.isArray(data) ? updatedServiceZones : updatedServiceZones[0] } updateShippingOptions( From 811cb7261c190aeef0171fef1d1142ce89a34889 Mon Sep 17 00:00:00 2001 From: adrien2p Date: Tue, 13 Feb 2024 14:43:44 +0100 Subject: [PATCH 18/29] add geo zones management --- .../fulfillment-module-service.spec.ts | 112 ++++++++++++++++++ .../services/fulfillment-module-service.ts | 64 +++++++++- packages/types/src/fulfillment/service.ts | 88 ++++++++++++++ 3 files changed, 262 insertions(+), 2 deletions(-) diff --git a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts index 5f1210393ba8c..a8edd8729d19c 100644 --- a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts +++ b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts @@ -952,4 +952,116 @@ describe("fulfillment module service", function () { expect(err.constraint).toBe("IDX_service_zone_name_unique") }) }) + + describe("on create geo zones", () => { + it("should create a new geo zone", async function () { + const data = { + type: GeoZoneType.COUNTRY, + country_code: "fr", + } + + const geoZone = await service.createGeoZones(data) + + expect(geoZone).toEqual( + expect.objectContaining({ + id: expect.any(String), + type: data.type, + country_code: data.country_code, + }) + ) + }) + + it("should create a collection of geo zones", async function () { + const data = [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + { + type: GeoZoneType.COUNTRY, + country_code: "us", + }, + ] + + const geoZones = await service.createGeoZones(data) + + expect(geoZones).toHaveLength(2) + + let i = 0 + for (const data_ of data) { + expect(geoZones[i]).toEqual( + expect.objectContaining({ + id: expect.any(String), + type: data_.type, + country_code: data_.country_code, + }) + ) + ++i + } + }) + }) + + describe("on update geo zones", () => { + it("should update an existing geo zone", async function () { + const createData = { + type: GeoZoneType.COUNTRY, + country_code: "fr", + } + + const createdGeoZone = await service.createGeoZones(createData) + + const updateData = { + id: createdGeoZone.id, + type: GeoZoneType.COUNTRY, + country_code: "us", + } + + const updatedGeoZone = await service.updateGeoZones(updateData) + + expect(updatedGeoZone).toEqual( + expect.objectContaining({ + id: updateData.id, + type: updateData.type, + country_code: updateData.country_code, + }) + ) + }) + + it("should update a collection of geo zones", async function () { + const createData = [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + { + type: GeoZoneType.COUNTRY, + country_code: "us", + }, + ] + + const createdGeoZones = await service.createGeoZones(createData) + + const updateData = createdGeoZones.map((geoZone, index) => ({ + id: geoZone.id, + type: GeoZoneType.COUNTRY, + country_code: index % 2 === 0 ? "us" : "fr", + })) + + const updatedGeoZones = await service.updateGeoZones(updateData) + + expect(updatedGeoZones).toHaveLength(2) + + let i = 0 + for (const data_ of updateData) { + expect(updatedGeoZones[i]).toEqual( + expect.objectContaining({ + id: data_.id, + type: data_.type, + country_code: data_.country_code, + }) + ) + ++i + } + }) + }) }) diff --git a/packages/fulfillment/src/services/fulfillment-module-service.ts b/packages/fulfillment/src/services/fulfillment-module-service.ts index f5f1b76fc02bc..f73ac360ebb02 100644 --- a/packages/fulfillment/src/services/fulfillment-module-service.ts +++ b/packages/fulfillment/src/services/fulfillment-module-service.ts @@ -18,7 +18,7 @@ import { import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config" import { FulfillmentSet, GeoZone, ServiceZone, ShippingOption } from "@models" -const generateMethodForModels = [ServiceZone, ShippingOption] +const generateMethodForModels = [ServiceZone, ShippingOption, GeoZone] type InjectedDependencies = { baseRepository: DAL.RepositoryService @@ -39,6 +39,7 @@ export default class FulfillmentModuleService< FulfillmentSet: { dto: FulfillmentTypes.FulfillmentSetDTO } ServiceZone: { dto: FulfillmentTypes.ServiceZoneDTO } ShippingOption: { dto: FulfillmentTypes.ShippingOptionDTO } + GeoZone: { dto: FulfillmentTypes.GeoZoneDTO } } >(FulfillmentSet, generateMethodForModels, entityNameToLinkableKeysMap) implements IFulfillmentModuleService @@ -554,6 +555,35 @@ export default class FulfillmentModuleService< return [] } + createGeoZones( + data: FulfillmentTypes.CreateGeoZoneDTO[], + sharedContext?: Context + ): Promise + createGeoZones( + data: FulfillmentTypes.CreateGeoZoneDTO, + sharedContext?: Context + ): Promise + + @InjectManager("baseRepository_") + async createGeoZones( + data: + | FulfillmentTypes.CreateGeoZoneDTO + | FulfillmentTypes.CreateGeoZoneDTO[], + sharedContext?: Context + ): Promise { + const createdGeoZones = await this.geoZoneService_.create( + data, + sharedContext + ) + + return await this.baseRepository_.serialize( + createdGeoZones, + { + populate: true, + } + ) + } + update( data: FulfillmentTypes.UpdateFulfillmentSetDTO[], sharedContext?: Context @@ -874,7 +904,7 @@ export default class FulfillmentModuleService< sharedContext ) - for (const [serviceZoneId, geoZoneMap] of serviceZoneGeoZonesMap) { + for (const [, geoZoneMap] of serviceZoneGeoZonesMap) { for (const createdGeoZone of createdGeoZones) { const geoZoneIdentifier = FulfillmentModuleService.getGeoZoneIdentifier(createdGeoZone, { @@ -924,4 +954,34 @@ export default class FulfillmentModuleService< > { return [] } + + updateGeoZones( + data: FulfillmentTypes.UpdateGeoZoneDTO[], + sharedContext?: Context + ): Promise + updateGeoZones( + data: FulfillmentTypes.UpdateGeoZoneDTO, + sharedContext?: Context + ): Promise + + @InjectManager("baseRepository_") + async updateGeoZones( + data: + | FulfillmentTypes.UpdateGeoZoneDTO + | FulfillmentTypes.UpdateGeoZoneDTO[], + sharedContext?: Context + ): Promise { + const updatedGeoZones = await this.geoZoneService_.update( + data, + sharedContext + ) + + const serialized = await this.baseRepository_.serialize< + FulfillmentTypes.GeoZoneDTO[] + >(updatedGeoZones, { + populate: true, + }) + + return Array.isArray(data) ? serialized : serialized[0] + } } diff --git a/packages/types/src/fulfillment/service.ts b/packages/types/src/fulfillment/service.ts index 034f65b9cca5c..ef47d0651d27a 100644 --- a/packages/types/src/fulfillment/service.ts +++ b/packages/types/src/fulfillment/service.ts @@ -1,9 +1,11 @@ import { IModuleService } from "../modules-sdk" import { FilterableFulfillmentSetProps, + FilterableGeoZoneProps, FilterableServiceZoneProps, FilterableShippingOptionProps, FulfillmentSetDTO, + GeoZoneDTO, ServiceZoneDTO, ShippingOptionDTO, } from "./common" @@ -12,9 +14,11 @@ import { Context } from "../shared-context" import { RestoreReturn, SoftDeleteReturn } from "../dal" import { CreateFulfillmentSetDTO, + CreateGeoZoneDTO, CreateServiceZoneDTO, CreateShippingOptionDTO, UpdateFulfillmentSetDTO, + UpdateGeoZoneDTO, UpdateServiceZoneDTO, UpdateShippingOptionDTO, } from "./mutations" @@ -62,6 +66,20 @@ export interface IFulfillmentModuleService extends IModuleService { sharedContext?: Context ): Promise + /** + * Create a new geo zone + * @param data + * @param sharedContext + */ + createGeoZones( + data: CreateGeoZoneDTO[], + sharedContext?: Context + ): Promise + createGeoZones( + data: CreateGeoZoneDTO, + sharedContext?: Context + ): Promise + /** * Update a fulfillment set * @param data @@ -104,6 +122,20 @@ export interface IFulfillmentModuleService extends IModuleService { sharedContext?: Context ): Promise + /** + * Update a geo zone + * @param data + * @param sharedContext + */ + updateGeoZones( + data: UpdateGeoZoneDTO[], + sharedContext?: Context + ): Promise + updateGeoZones( + data: UpdateGeoZoneDTO, + sharedContext?: Context + ): Promise + /** * Delete a fulfillment set * @param ids @@ -128,6 +160,14 @@ export interface IFulfillmentModuleService extends IModuleService { deleteShippingOptions(ids: string[], sharedContext?: Context): Promise deleteShippingOptions(id: string, sharedContext?: Context): Promise + /** + * Delete a geo zone + * @param ids + * @param sharedContext + */ + deleteGeoZones(ids: string[], sharedContext?: Context): Promise + deleteGeoZones(id: string, sharedContext?: Context): Promise + /** * Retrieve a fulfillment set * @param id @@ -164,6 +204,18 @@ export interface IFulfillmentModuleService extends IModuleService { sharedContext?: Context ): Promise + /** + * Retrieve a geo zone + * @param id + * @param config + * @param sharedContext + */ + retrieveGeoZone( + id: string, + config?: FindConfig, + sharedContext?: Context + ): Promise + /** * List fulfillment sets * @param filters @@ -200,6 +252,18 @@ export interface IFulfillmentModuleService extends IModuleService { sharedContext?: Context ): Promise + /** + * List geo zones + * @param filters + * @param config + * @param sharedContext + */ + listGeoZones( + filters?: FilterableGeoZoneProps, + config?: FindConfig, + sharedContext?: Context + ): Promise + /** * List and count fulfillment sets * @param filters @@ -236,6 +300,18 @@ export interface IFulfillmentModuleService extends IModuleService { sharedContext?: Context ): Promise<[ShippingOptionDTO[], number]> + /** + * List and count geo zones + * @param filters + * @param config + * @param sharedContext + */ + listAndCountGeoZones( + filters?: FilterableGeoZoneProps, + config?: FindConfig, + sharedContext?: Context + ): Promise<[GeoZoneDTO[], number]> + /** * Soft delete fulfillment sets * @param fulfillmentIds @@ -272,6 +348,18 @@ export interface IFulfillmentModuleService extends IModuleService { sharedContext?: Context ): Promise | void> + /** + * Soft delete geo zones + * @param geoZoneIds + * @param config + * @param sharedContext + */ + softDeleteGeoZones( + geoZoneIds: string[], + config?: SoftDeleteReturn, + sharedContext?: Context + ): Promise | void> + restore( fulfillmentIds: string[], config?: RestoreReturn, From 9e563d8b7dcc5b164f267bbf186f5882930ea664 Mon Sep 17 00:00:00 2001 From: adrien2p Date: Tue, 13 Feb 2024 14:45:20 +0100 Subject: [PATCH 19/29] cleanup --- packages/fulfillment/jest.config.js | 1 - packages/fulfillment/src/repositories/service-zone.ts | 0 2 files changed, 1 deletion(-) delete mode 100644 packages/fulfillment/src/repositories/service-zone.ts diff --git a/packages/fulfillment/jest.config.js b/packages/fulfillment/jest.config.js index 4f5c805538355..456054fe8ae27 100644 --- a/packages/fulfillment/jest.config.js +++ b/packages/fulfillment/jest.config.js @@ -4,7 +4,6 @@ module.exports = { "^@services": "/src/services", "^@repositories": "/src/repositories", "^@types": "/src/types", - "^@migrations": "/src/migrations", }, transform: { "^.+\\.[jt]s?$": [ diff --git a/packages/fulfillment/src/repositories/service-zone.ts b/packages/fulfillment/src/repositories/service-zone.ts deleted file mode 100644 index e69de29bb2d1d..0000000000000 From 7720ba5b674f148801fd11a08fa3f7cb9ca74d22 Mon Sep 17 00:00:00 2001 From: adrien2p Date: Tue, 13 Feb 2024 15:03:46 +0100 Subject: [PATCH 20/29] add read tests --- .../fulfillment-module-service.spec.ts | 1656 +++++++++-------- 1 file changed, 910 insertions(+), 746 deletions(-) diff --git a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts index a8edd8729d19c..e3a22217e9a2b 100644 --- a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts +++ b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts @@ -33,84 +33,218 @@ describe("fulfillment module service", function () { await shutdownFunc() }) - describe("on create", () => { - it("should create a new fulfillment set", async function () { - const data: CreateFulfillmentSetDTO = { - name: "test", - type: "test-type", - } - - const fulfillmentSet = await service.create(data) - - expect(fulfillmentSet).toEqual( - expect.objectContaining({ - id: expect.any(String), - name: data.name, - type: data.type, + describe("read", () => { + describe("fulfillment set", () => { + it("should list fulfillment sets with a filter", async function () { + const createdSet1 = await service.create({ + name: "test", + type: "test-type", + }) + const createdSet2 = await service.create({ + name: "test2", + type: "test-type", + service_zones: [ + { + name: "test", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + ], + }, + ], }) - ) + + let listedSets = await service.list({ type: createdSet1.type }) + + expect(listedSets).toEqual( + expect.arrayContaining([ + expect.objectContaining({ id: createdSet1.id }), + expect.objectContaining({ id: createdSet2.id }), + ]) + ) + + listedSets = await service.list({ name: createdSet2.name }) + + expect(listedSets).toEqual( + expect.arrayContaining([ + expect.objectContaining({ id: createdSet2.id }), + ]) + ) + expect(listedSets).not.toEqual( + expect.arrayContaining([ + expect.objectContaining({ id: createdSet1.id }), + ]) + ) + + listedSets = await service.list({ service_zones: { name: "test" } }) + + expect(listedSets).toEqual( + expect.arrayContaining([ + expect.objectContaining({ id: createdSet2.id }), + ]) + ) + expect(listedSets).not.toEqual( + expect.arrayContaining([ + expect.objectContaining({ id: createdSet1.id }), + ]) + ) + + listedSets = await service.list({ + service_zones: { geo_zones: { country_code: "fr" } }, + }) + + expect(listedSets).toEqual( + expect.arrayContaining([ + expect.objectContaining({ id: createdSet2.id }), + ]) + ) + expect(listedSets).not.toEqual( + expect.arrayContaining([ + expect.objectContaining({ id: createdSet1.id }), + ]) + ) + }) }) - it("should create a collection of fulfillment sets", async function () { - const data = [ - { + describe("service zones", () => { + it("should list service zones with a filter", async function () { + const createdZone1 = await service.createServiceZones({ name: "test", - type: "test-type", - }, - { + }) + const createdZone2 = await service.createServiceZones({ name: "test2", - type: "test-type2", - }, - ] + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + ], + }) + + let listedZones = await service.listServiceZones({ + name: createdZone2.name, + }) - const fulfillmentSets = await service.create(data) + expect(listedZones).toEqual( + expect.arrayContaining([ + expect.objectContaining({ id: createdZone2.id }), + ]) + ) + expect(listedZones).not.toEqual( + expect.arrayContaining([ + expect.objectContaining({ id: createdZone1.id }), + ]) + ) - expect(fulfillmentSets).toHaveLength(2) + listedZones = await service.listServiceZones({ + geo_zones: { country_code: "fr" }, + }) - let i = 0 - for (const data_ of data) { - expect(fulfillmentSets[i]).toEqual( + expect(listedZones).toEqual( + expect.arrayContaining([ + expect.objectContaining({ id: createdZone2.id }), + ]) + ) + expect(listedZones).not.toEqual( + expect.arrayContaining([ + expect.objectContaining({ id: createdZone1.id }), + ]) + ) + }) + }) + + describe("geo zones", () => { + it("should list geo zones with a filter", async function () { + const createdZone1 = await service.createGeoZones({ + type: GeoZoneType.COUNTRY, + country_code: "fr", + }) + const createdZone2 = await service.createGeoZones({ + type: GeoZoneType.COUNTRY, + country_code: "us", + }) + + let listedZones = await service.listGeoZones({ + type: createdZone1.type, + }) + + expect(listedZones).toEqual( + expect.arrayContaining([ + expect.objectContaining({ id: createdZone1.id }), + expect.objectContaining({ id: createdZone2.id }), + ]) + ) + + listedZones = await service.listGeoZones({ + country_code: createdZone2.country_code, + }) + + expect(listedZones).toEqual( + expect.arrayContaining([ + expect.objectContaining({ id: createdZone2.id }), + ]) + ) + expect(listedZones).not.toEqual( + expect.arrayContaining([ + expect.objectContaining({ id: createdZone1.id }), + ]) + ) + }) + }) + }) + + describe("mutations", () => { + describe("on create", () => { + it("should create a new fulfillment set", async function () { + const data: CreateFulfillmentSetDTO = { + name: "test", + type: "test-type", + } + + const fulfillmentSet = await service.create(data) + + expect(fulfillmentSet).toEqual( expect.objectContaining({ id: expect.any(String), - name: data_.name, - type: data_.type, + name: data.name, + type: data.type, }) ) - ++i - } - }) + }) - it("should create a new fulfillment set with new service zones", async function () { - const data = { - name: "test", - type: "test-type", - service_zones: [ + it("should create a collection of fulfillment sets", async function () { + const data = [ { name: "test", + type: "test-type", + }, + { + name: "test2", + type: "test-type2", }, - ], - } + ] + + const fulfillmentSets = await service.create(data) - const fulfillmentSet = await service.create(data) + expect(fulfillmentSets).toHaveLength(2) - expect(fulfillmentSet).toEqual( - expect.objectContaining({ - id: expect.any(String), - name: data.name, - type: data.type, - service_zones: expect.arrayContaining([ + let i = 0 + for (const data_ of data) { + expect(fulfillmentSets[i]).toEqual( expect.objectContaining({ id: expect.any(String), - name: data.service_zones[0].name, - }), - ]), - }) - ) - }) - - it("should create a collection of fulfillment sets with new service zones", async function () { - const data = [ - { + name: data_.name, + type: data_.type, + }) + ) + ++i + } + }) + + it("should create a new fulfillment set with new service zones", async function () { + const data = { name: "test", type: "test-type", service_zones: [ @@ -118,99 +252,86 @@ describe("fulfillment module service", function () { name: "test", }, ], - }, - { - name: "test2", - type: "test-type2", - service_zones: [ - { - name: "test", - }, - ], - }, - { - name: "test3", - type: "test-type3", - service_zones: [ - { - name: "test2", - }, - ], - }, - ] - - const fulfillmentSets = await service.create(data) + } - expect(fulfillmentSets).toHaveLength(3) + const fulfillmentSet = await service.create(data) - let i = 0 - for (const data_ of data) { - expect(fulfillmentSets[i]).toEqual( + expect(fulfillmentSet).toEqual( expect.objectContaining({ id: expect.any(String), - name: data_.name, - type: data_.type, + name: data.name, + type: data.type, service_zones: expect.arrayContaining([ expect.objectContaining({ id: expect.any(String), - name: data_.service_zones[0].name, + name: data.service_zones[0].name, }), ]), }) ) - ++i - } + }) - // expect the first and second fulfillment set to have the same service zone - expect(fulfillmentSets[0].service_zones[0].id).toEqual( - fulfillmentSets[1].service_zones[0].id - ) - }) - - it("should create a new fulfillment set with new service zones and new geo zones", async function () { - const data: CreateFulfillmentSetDTO = { - name: "test", - type: "test-type", - service_zones: [ + it("should create a collection of fulfillment sets with new service zones", async function () { + const data = [ { name: "test", - geo_zones: [ + type: "test-type", + service_zones: [ { - type: GeoZoneType.COUNTRY, - country_code: "fr", + name: "test", + }, + ], + }, + { + name: "test2", + type: "test-type2", + service_zones: [ + { + name: "test", }, ], }, - ], - } + { + name: "test3", + type: "test-type3", + service_zones: [ + { + name: "test2", + }, + ], + }, + ] - const fulfillmentSet = await service.create(data) + const fulfillmentSets = await service.create(data) - expect(fulfillmentSet).toEqual( - expect.objectContaining({ - id: expect.any(String), - name: data.name, - type: data.type, - service_zones: expect.arrayContaining([ + expect(fulfillmentSets).toHaveLength(3) + + let i = 0 + for (const data_ of data) { + expect(fulfillmentSets[i]).toEqual( expect.objectContaining({ id: expect.any(String), - name: (data.service_zones![0] as any).name, - geo_zones: expect.arrayContaining([ + name: data_.name, + type: data_.type, + service_zones: expect.arrayContaining([ expect.objectContaining({ - type: (data.service_zones![0] as any).geo_zones[0].type, - country_code: (data.service_zones![0] as any).geo_zones[0] - .country_code, + id: expect.any(String), + name: data_.service_zones[0].name, }), ]), - }), - ]), - }) - ) - }) + }) + ) + ++i + } + + // expect the first and second fulfillment set to have the same service zone + expect(fulfillmentSets[0].service_zones[0].id).toEqual( + fulfillmentSets[1].service_zones[0].id + ) + }) - it("should create a collection of fulfillment sets with new service zones and new geo zones", async function () { - const data: CreateFulfillmentSetDTO[] = [ - { + it("should create a new fulfillment set with new service zones and new geo zones", async function () { + const data: CreateFulfillmentSetDTO = { name: "test", type: "test-type", service_zones: [ @@ -224,59 +345,23 @@ describe("fulfillment module service", function () { ], }, ], - }, - { - name: "test2", - type: "test-type2", - service_zones: [ - { - name: "test2", - geo_zones: [ - { - type: GeoZoneType.COUNTRY, - country_code: "fr", - }, - ], - }, - ], - }, - { - name: "test3", - type: "test-type3", - service_zones: [ - { - name: "test3", - geo_zones: [ - { - type: GeoZoneType.CITY, - country_code: "fr", - city: "lyon", - }, - ], - }, - ], - }, - ] + } - const fulfillmentSets = await service.create(data) + const fulfillmentSet = await service.create(data) - expect(fulfillmentSets).toHaveLength(3) - - let i = 0 - for (const data_ of data) { - expect(fulfillmentSets[i]).toEqual( + expect(fulfillmentSet).toEqual( expect.objectContaining({ id: expect.any(String), - name: data_.name, - type: data_.type, + name: data.name, + type: data.type, service_zones: expect.arrayContaining([ expect.objectContaining({ id: expect.any(String), - name: (data_.service_zones![0] as any).name, + name: (data.service_zones![0] as any).name, geo_zones: expect.arrayContaining([ expect.objectContaining({ - type: (data_.service_zones![0] as any).geo_zones[0].type, - country_code: (data_.service_zones![0] as any).geo_zones[0] + type: (data.service_zones![0] as any).geo_zones[0].type, + country_code: (data.service_zones![0] as any).geo_zones[0] .country_code, }), ]), @@ -284,60 +369,110 @@ describe("fulfillment module service", function () { ]), }) ) - ++i - } + }) - // expect the first and second fulfillment set to have the same geo zone for their service zone - expect(fulfillmentSets[0].service_zones[0].geo_zones[0].id).toEqual( - fulfillmentSets[1].service_zones[0].geo_zones[0].id - ) - }) + it("should create a collection of fulfillment sets with new service zones and new geo zones", async function () { + const data: CreateFulfillmentSetDTO[] = [ + { + name: "test", + type: "test-type", + service_zones: [ + { + name: "test", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + ], + }, + ], + }, + { + name: "test2", + type: "test-type2", + service_zones: [ + { + name: "test2", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + ], + }, + ], + }, + { + name: "test3", + type: "test-type3", + service_zones: [ + { + name: "test3", + geo_zones: [ + { + type: GeoZoneType.CITY, + country_code: "fr", + city: "lyon", + }, + ], + }, + ], + }, + ] - it(`should fail on duplicated fulfillment set name`, async function () { - const data: CreateFulfillmentSetDTO = { - name: "test", - type: "test-type", - } + const fulfillmentSets = await service.create(data) - await service.create(data) - const err = await service.create(data).catch((e) => e) + expect(fulfillmentSets).toHaveLength(3) - expect(err).toBeDefined() - expect(err.constraint).toBe("IDX_fulfillment_set_name_unique") - }) - }) + let i = 0 + for (const data_ of data) { + expect(fulfillmentSets[i]).toEqual( + expect.objectContaining({ + id: expect.any(String), + name: data_.name, + type: data_.type, + service_zones: expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + name: (data_.service_zones![0] as any).name, + geo_zones: expect.arrayContaining([ + expect.objectContaining({ + type: (data_.service_zones![0] as any).geo_zones[0].type, + country_code: (data_.service_zones![0] as any) + .geo_zones[0].country_code, + }), + ]), + }), + ]), + }) + ) + ++i + } + + // expect the first and second fulfillment set to have the same geo zone for their service zone + expect(fulfillmentSets[0].service_zones[0].geo_zones[0].id).toEqual( + fulfillmentSets[1].service_zones[0].geo_zones[0].id + ) + }) - describe("on create service zones", () => { - it("should create a new service zone", async function () { - const data = { - name: "test", - geo_zones: [ - { - type: GeoZoneType.COUNTRY, - country_code: "fr", - }, - ], - } + it(`should fail on duplicated fulfillment set name`, async function () { + const data: CreateFulfillmentSetDTO = { + name: "test", + type: "test-type", + } - const serviceZone = await service.createServiceZones(data) + await service.create(data) + const err = await service.create(data).catch((e) => e) - expect(serviceZone).toEqual( - expect.objectContaining({ - id: expect.any(String), - name: data.name, - geo_zones: expect.arrayContaining([ - expect.objectContaining({ - type: data.geo_zones[0].type, - country_code: data.geo_zones[0].country_code, - }), - ]), - }) - ) + expect(err).toBeDefined() + expect(err.constraint).toBe("IDX_fulfillment_set_name_unique") + }) }) - it("should create a collection of service zones", async function () { - const data = [ - { + describe("on create service zones", () => { + it("should create a new service zone", async function () { + const data = { name: "test", geo_zones: [ { @@ -345,145 +480,28 @@ describe("fulfillment module service", function () { country_code: "fr", }, ], - }, - { - name: "test2", - geo_zones: [ - { - type: GeoZoneType.COUNTRY, - country_code: "fr", - }, - ], - }, - { - name: "test3", - geo_zones: [ - { - type: GeoZoneType.COUNTRY, - country_code: "uk", - }, - ], - }, - ] + } - const serviceZones = await service.createServiceZones(data) + const serviceZone = await service.createServiceZones(data) - expect(serviceZones).toHaveLength(3) - - let i = 0 - for (const data_ of data) { - expect(serviceZones[i]).toEqual( + expect(serviceZone).toEqual( expect.objectContaining({ id: expect.any(String), - name: data_.name, + name: data.name, geo_zones: expect.arrayContaining([ expect.objectContaining({ - type: data_.geo_zones[0].type, - country_code: data_.geo_zones[0].country_code, + type: data.geo_zones[0].type, + country_code: data.geo_zones[0].country_code, }), ]), }) ) - ++i - } - - // expect the first and second service zone to have the same geo zone - expect(serviceZones[0].geo_zones[0].id).toEqual( - serviceZones[1].geo_zones[0].id - ) - }) + }) - it("should fail on duplicated service zone name", async function () { - const data = { - name: "test", - geo_zones: [ + it("should create a collection of service zones", async function () { + const data = [ { - type: GeoZoneType.COUNTRY, - country_code: "fr", - }, - ], - } - - await service.createServiceZones(data) - const err = await service.createServiceZones(data).catch((e) => e) - - expect(err).toBeDefined() - expect(err.constraint).toBe("IDX_service_zone_name_unique") - }) - }) - - describe("on update", () => { - it("should update an existing fulfillment set", async function () { - const createData: CreateFulfillmentSetDTO = { - name: "test", - type: "test-type", - } - - const createdFulfillmentSet = await service.create(createData) - - const updateData = { - id: createdFulfillmentSet.id, - name: "updated-test", - type: "updated-test-type", - } - - const updatedFulfillmentSets = await service.update(updateData) - - expect(updatedFulfillmentSets).toEqual( - expect.objectContaining({ - id: createdFulfillmentSet.id, - name: updateData.name, - type: updateData.type, - }) - ) - }) - - it("should update a collection of fulfillment sets", async function () { - const createData = [ - { - name: "test", - type: "test-type", - }, - { - name: "test2", - type: "test-type2", - }, - ] - - const createdFulfillmentSets = await service.create(createData) - - const updateData = createdFulfillmentSets.map( - (fulfillmentSet, index) => ({ - id: fulfillmentSet.id, - name: `updated-test${index + 1}`, - type: `updated-test-type${index + 1}`, - }) - ) - - const updatedFulfillmentSets = await service.update(updateData) - - expect(updatedFulfillmentSets).toHaveLength(2) - - let i = 0 - for (const data_ of updateData) { - expect(updatedFulfillmentSets[i]).toEqual( - expect.objectContaining({ - id: createdFulfillmentSets[i].id, - name: data_.name, - type: data_.type, - }) - ) - ++i - } - }) - - it("should update an existing fulfillment set and replace old service zones by a new one", async function () { - const createData: CreateFulfillmentSetDTO = { - name: "test", - type: "test-type", - service_zones: [ - { - name: "service-zone-test", + name: "test", geo_zones: [ { type: GeoZoneType.COUNTRY, @@ -491,154 +509,144 @@ describe("fulfillment module service", function () { }, ], }, - ], - } - - const createdFulfillmentSet = await service.create(createData) - - const createServiceZoneData = { - name: "service-zone-test2", - geo_zones: [ { - type: GeoZoneType.COUNTRY, - country_code: "us", + name: "test2", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + ], }, - ], - } - - const updateData = { - id: createdFulfillmentSet.id, - name: "updated-test", - type: "updated-test-type", - service_zones: [createServiceZoneData], - } - - const updatedFulfillmentSet = await service.update(updateData) - - expect(updatedFulfillmentSet).toEqual( - expect.objectContaining({ - id: updateData.id, - name: updateData.name, - type: updateData.type, - service_zones: expect.arrayContaining([ - expect.objectContaining({ - id: expect.any(String), - name: updateData.service_zones[0].name, - geo_zones: expect.arrayContaining([ - expect.objectContaining({ - id: expect.any(String), - type: updateData.service_zones[0].geo_zones[0].type, - country_code: - updateData.service_zones[0].geo_zones[0].country_code, - }), - ]), - }), - ]), - }) - ) - }) - - it("should update an existing fulfillment set and add a new service zone", async function () { - const createData: CreateFulfillmentSetDTO = { - name: "test", - type: "test-type", - service_zones: [ { - name: "service-zone-test", + name: "test3", geo_zones: [ { type: GeoZoneType.COUNTRY, - country_code: "fr", + country_code: "uk", }, ], }, - ], - } + ] - const createdFulfillmentSet = await service.create(createData) + const serviceZones = await service.createServiceZones(data) - const createServiceZoneData = { - name: "service-zone-test2", - geo_zones: [ - { - type: GeoZoneType.COUNTRY, - country_code: "us", - }, - ], - } - - const updateData = { - id: createdFulfillmentSet.id, - name: "updated-test", - type: "updated-test-type", - service_zones: [ - { id: createdFulfillmentSet.service_zones[0].id }, - createServiceZoneData, - ], - } - - const updatedFulfillmentSet = await service.update(updateData) - - expect(updatedFulfillmentSet).toEqual( - expect.objectContaining({ - id: updateData.id, - name: updateData.name, - type: updateData.type, - service_zones: expect.arrayContaining([ - expect.objectContaining({ - id: createdFulfillmentSet.service_zones[0].id, - }), + expect(serviceZones).toHaveLength(3) + + let i = 0 + for (const data_ of data) { + expect(serviceZones[i]).toEqual( expect.objectContaining({ id: expect.any(String), - name: updateData.service_zones[1].name, + name: data_.name, geo_zones: expect.arrayContaining([ expect.objectContaining({ - id: expect.any(String), - type: updateData.service_zones[1].geo_zones[0].type, - country_code: - updateData.service_zones[1].geo_zones[0].country_code, + type: data_.geo_zones[0].type, + country_code: data_.geo_zones[0].country_code, }), ]), - }), - ]), - }) - ) + }) + ) + ++i + } + + // expect the first and second service zone to have the same geo zone + expect(serviceZones[0].geo_zones[0].id).toEqual( + serviceZones[1].geo_zones[0].id + ) + }) + + it("should fail on duplicated service zone name", async function () { + const data = { + name: "test", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + ], + } + + await service.createServiceZones(data) + const err = await service.createServiceZones(data).catch((e) => e) + + expect(err).toBeDefined() + expect(err.constraint).toBe("IDX_service_zone_name_unique") + }) }) - it("should fail on duplicated fulfillment set name", async function () { - const createData = [ - { + describe("on update", () => { + it("should update an existing fulfillment set", async function () { + const createData: CreateFulfillmentSetDTO = { name: "test", type: "test-type", - }, - { - name: "test2", - type: "test-type2", - }, - ] + } - const createdFulfillmentSets = await service.create(createData) + const createdFulfillmentSet = await service.create(createData) - const updateData = { - id: createdFulfillmentSets[1].id, - name: "test", // This is the name of the first fulfillment set - type: "updated-test-type2", - } + const updateData = { + id: createdFulfillmentSet.id, + name: "updated-test", + type: "updated-test-type", + } - const err = await service.update(updateData).catch((e) => e) + const updatedFulfillmentSets = await service.update(updateData) - expect(err).toBeDefined() - expect(err.constraint).toBe("IDX_fulfillment_set_name_unique") - }) + expect(updatedFulfillmentSets).toEqual( + expect.objectContaining({ + id: createdFulfillmentSet.id, + name: updateData.name, + type: updateData.type, + }) + ) + }) - it("should update a collection of fulfillment sets and replace old service zones by new ones", async function () { - const createData: CreateFulfillmentSetDTO[] = [ - { - name: "test1", - type: "test-type1", + it("should update a collection of fulfillment sets", async function () { + const createData = [ + { + name: "test", + type: "test-type", + }, + { + name: "test2", + type: "test-type2", + }, + ] + + const createdFulfillmentSets = await service.create(createData) + + const updateData = createdFulfillmentSets.map( + (fulfillmentSet, index) => ({ + id: fulfillmentSet.id, + name: `updated-test${index + 1}`, + type: `updated-test-type${index + 1}`, + }) + ) + + const updatedFulfillmentSets = await service.update(updateData) + + expect(updatedFulfillmentSets).toHaveLength(2) + + let i = 0 + for (const data_ of updateData) { + expect(updatedFulfillmentSets[i]).toEqual( + expect.objectContaining({ + id: createdFulfillmentSets[i].id, + name: data_.name, + type: data_.type, + }) + ) + ++i + } + }) + + it("should update an existing fulfillment set and replace old service zones by a new one", async function () { + const createData: CreateFulfillmentSetDTO = { + name: "test", + type: "test-type", service_zones: [ { - name: "service-zone-test1", + name: "service-zone-test", geo_zones: [ { type: GeoZoneType.COUNTRY, @@ -647,84 +655,59 @@ describe("fulfillment module service", function () { ], }, ], - }, - { - name: "test2", - type: "test-type2", - service_zones: [ - { - name: "service-zone-test2", - geo_zones: [ - { - type: GeoZoneType.COUNTRY, - country_code: "us", - }, - ], - }, - ], - }, - ] + } - const createdFulfillmentSets = await service.create(createData) + const createdFulfillmentSet = await service.create(createData) - const updateData = createdFulfillmentSets.map( - (fulfillmentSet, index) => ({ - id: fulfillmentSet.id, - name: `updated-test${index + 1}`, - type: `updated-test-type${index + 1}`, - service_zones: [ + const createServiceZoneData = { + name: "service-zone-test2", + geo_zones: [ { - name: `new-service-zone-test`, - geo_zones: [ - { - type: GeoZoneType.COUNTRY, - country_code: "test", - }, - ], + type: GeoZoneType.COUNTRY, + country_code: "us", }, ], - }) - ) + } - const updatedFulfillmentSets = await service.update(updateData) + const updateData = { + id: createdFulfillmentSet.id, + name: "updated-test", + type: "updated-test-type", + service_zones: [createServiceZoneData], + } - expect(updatedFulfillmentSets).toHaveLength(2) + const updatedFulfillmentSet = await service.update(updateData) - let i = 0 - for (const data_ of updateData) { - expect(updatedFulfillmentSets[i]).toEqual( + expect(updatedFulfillmentSet).toEqual( expect.objectContaining({ - id: data_.id, - name: data_.name, - type: data_.type, + id: updateData.id, + name: updateData.name, + type: updateData.type, service_zones: expect.arrayContaining([ expect.objectContaining({ id: expect.any(String), - name: data_.service_zones[0].name, + name: updateData.service_zones[0].name, geo_zones: expect.arrayContaining([ expect.objectContaining({ id: expect.any(String), - type: data_.service_zones[0].geo_zones[0].type, + type: updateData.service_zones[0].geo_zones[0].type, country_code: - data_.service_zones[0].geo_zones[0].country_code, + updateData.service_zones[0].geo_zones[0].country_code, }), ]), }), ]), }) ) - ++i - } - }) + }) - it("should update a collection of fulfillment sets and add new service zones", async function () { - const createData: CreateFulfillmentSetDTO[] = [ - { - name: "test1", - type: "test-type1", + it("should update an existing fulfillment set and add a new service zone", async function () { + const createData: CreateFulfillmentSetDTO = { + name: "test", + type: "test-type", service_zones: [ { - name: "service-zone-test1", + name: "service-zone-test", geo_zones: [ { type: GeoZoneType.COUNTRY, @@ -733,127 +716,264 @@ describe("fulfillment module service", function () { ], }, ], - }, - { - name: "test2", - type: "test-type2", - service_zones: [ - { - name: "service-zone-test2", - geo_zones: [ - { - type: GeoZoneType.COUNTRY, - country_code: "us", - }, - ], - }, - ], - }, - ] + } - const createdFulfillmentSets = await service.create(createData) + const createdFulfillmentSet = await service.create(createData) - const updateData = createdFulfillmentSets.map( - (fulfillmentSet, index) => ({ - id: fulfillmentSet.id, - name: `updated-test${index + 1}`, - type: `updated-test-type${index + 1}`, - service_zones: [ - ...fulfillmentSet.service_zones, + const createServiceZoneData = { + name: "service-zone-test2", + geo_zones: [ { - name: `added-service-zone-test`, - geo_zones: [ - { - type: GeoZoneType.COUNTRY, - country_code: "test", - }, - ], + type: GeoZoneType.COUNTRY, + country_code: "us", }, ], - }) - ) + } - const updatedFulfillmentSets = await service.update(updateData) + const updateData = { + id: createdFulfillmentSet.id, + name: "updated-test", + type: "updated-test-type", + service_zones: [ + { id: createdFulfillmentSet.service_zones[0].id }, + createServiceZoneData, + ], + } - expect(updatedFulfillmentSets).toHaveLength(2) + const updatedFulfillmentSet = await service.update(updateData) - let i = 0 - for (const data_ of updateData) { - expect(updatedFulfillmentSets[i]).toEqual( + expect(updatedFulfillmentSet).toEqual( expect.objectContaining({ - id: data_.id, - name: data_.name, - type: data_.type, + id: updateData.id, + name: updateData.name, + type: updateData.type, service_zones: expect.arrayContaining([ expect.objectContaining({ - id: createdFulfillmentSets[i].service_zones[0].id, + id: createdFulfillmentSet.service_zones[0].id, }), expect.objectContaining({ id: expect.any(String), - name: data_.service_zones[1].name, + name: updateData.service_zones[1].name, geo_zones: expect.arrayContaining([ expect.objectContaining({ id: expect.any(String), - type: data_.service_zones[1].geo_zones[0].type, + type: updateData.service_zones[1].geo_zones[0].type, country_code: - data_.service_zones[1].geo_zones[0].country_code, + updateData.service_zones[1].geo_zones[0].country_code, }), ]), }), ]), }) ) - ++i - } - }) - }) + }) - describe("on update service zones", () => { - it("should update an existing service zone", async function () { - const createData = { - name: "service-zone-test", - geo_zones: [ + it("should fail on duplicated fulfillment set name", async function () { + const createData = [ { - type: GeoZoneType.COUNTRY, - country_code: "fr", + name: "test", + type: "test-type", + }, + { + name: "test2", + type: "test-type2", }, - ], - } + ] - const createdServiceZone = await service.createServiceZones(createData) + const createdFulfillmentSets = await service.create(createData) - const updateData = { - id: createdServiceZone.id, - name: "updated-service-zone-test", - geo_zones: [ + const updateData = { + id: createdFulfillmentSets[1].id, + name: "test", // This is the name of the first fulfillment set + type: "updated-test-type2", + } + + const err = await service.update(updateData).catch((e) => e) + + expect(err).toBeDefined() + expect(err.constraint).toBe("IDX_fulfillment_set_name_unique") + }) + + it("should update a collection of fulfillment sets and replace old service zones by new ones", async function () { + const createData: CreateFulfillmentSetDTO[] = [ { - id: createdServiceZone.geo_zones[0].id, - type: GeoZoneType.COUNTRY, - country_code: "us", + name: "test1", + type: "test-type1", + service_zones: [ + { + name: "service-zone-test1", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + ], + }, + ], + }, + { + name: "test2", + type: "test-type2", + service_zones: [ + { + name: "service-zone-test2", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "us", + }, + ], + }, + ], }, - ], - } + ] - const updatedServiceZone = await service.updateServiceZones(updateData) + const createdFulfillmentSets = await service.create(createData) - expect(updatedServiceZone).toEqual( - expect.objectContaining({ - id: updateData.id, - name: updateData.name, - geo_zones: expect.arrayContaining([ + const updateData = createdFulfillmentSets.map( + (fulfillmentSet, index) => ({ + id: fulfillmentSet.id, + name: `updated-test${index + 1}`, + type: `updated-test-type${index + 1}`, + service_zones: [ + { + name: `new-service-zone-test`, + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "test", + }, + ], + }, + ], + }) + ) + + const updatedFulfillmentSets = await service.update(updateData) + + expect(updatedFulfillmentSets).toHaveLength(2) + + let i = 0 + for (const data_ of updateData) { + expect(updatedFulfillmentSets[i]).toEqual( expect.objectContaining({ - id: updateData.geo_zones[0].id, - type: updateData.geo_zones[0].type, - country_code: updateData.geo_zones[0].country_code, - }), - ]), - }) - ) + id: data_.id, + name: data_.name, + type: data_.type, + service_zones: expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + name: data_.service_zones[0].name, + geo_zones: expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + type: data_.service_zones[0].geo_zones[0].type, + country_code: + data_.service_zones[0].geo_zones[0].country_code, + }), + ]), + }), + ]), + }) + ) + ++i + } + }) + + it("should update a collection of fulfillment sets and add new service zones", async function () { + const createData: CreateFulfillmentSetDTO[] = [ + { + name: "test1", + type: "test-type1", + service_zones: [ + { + name: "service-zone-test1", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + ], + }, + ], + }, + { + name: "test2", + type: "test-type2", + service_zones: [ + { + name: "service-zone-test2", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "us", + }, + ], + }, + ], + }, + ] + + const createdFulfillmentSets = await service.create(createData) + + const updateData = createdFulfillmentSets.map( + (fulfillmentSet, index) => ({ + id: fulfillmentSet.id, + name: `updated-test${index + 1}`, + type: `updated-test-type${index + 1}`, + service_zones: [ + ...fulfillmentSet.service_zones, + { + name: `added-service-zone-test`, + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "test", + }, + ], + }, + ], + }) + ) + + const updatedFulfillmentSets = await service.update(updateData) + + expect(updatedFulfillmentSets).toHaveLength(2) + + let i = 0 + for (const data_ of updateData) { + expect(updatedFulfillmentSets[i]).toEqual( + expect.objectContaining({ + id: data_.id, + name: data_.name, + type: data_.type, + service_zones: expect.arrayContaining([ + expect.objectContaining({ + id: createdFulfillmentSets[i].service_zones[0].id, + }), + expect.objectContaining({ + id: expect.any(String), + name: data_.service_zones[1].name, + geo_zones: expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + type: data_.service_zones[1].geo_zones[0].type, + country_code: + data_.service_zones[1].geo_zones[0].country_code, + }), + ]), + }), + ]), + }) + ) + ++i + } + }) }) - it("should update a collection of service zones", async function () { - const createData = [ - { + describe("on update service zones", () => { + it("should update an existing service zone", async function () { + const createData = { name: "service-zone-test", geo_zones: [ { @@ -861,207 +981,251 @@ describe("fulfillment module service", function () { country_code: "fr", }, ], - }, - { - name: "service-zone-test2", + } + + const createdServiceZone = await service.createServiceZones(createData) + + const updateData = { + id: createdServiceZone.id, + name: "updated-service-zone-test", geo_zones: [ { + id: createdServiceZone.geo_zones[0].id, type: GeoZoneType.COUNTRY, country_code: "us", }, ], - }, - ] - - const createdServiceZones = await service.createServiceZones(createData) - - const updateData = createdServiceZones.map((serviceZone, index) => ({ - id: serviceZone.id, - name: `updated-service-zone-test${index + 1}`, - geo_zones: [ - { - id: serviceZone.geo_zones[0].id, - type: GeoZoneType.COUNTRY, - country_code: index % 2 === 0 ? "us" : "fr", - }, - ], - })) + } - const updatedServiceZones = await service.updateServiceZones(updateData) + const updatedServiceZone = await service.updateServiceZones(updateData) - expect(updatedServiceZones).toHaveLength(2) - - let i = 0 - for (const data_ of updateData) { - expect(updatedServiceZones[i]).toEqual( + expect(updatedServiceZone).toEqual( expect.objectContaining({ - id: data_.id, - name: data_.name, + id: updateData.id, + name: updateData.name, geo_zones: expect.arrayContaining([ expect.objectContaining({ - id: data_.geo_zones[0].id, - type: data_.geo_zones[0].type, - country_code: data_.geo_zones[0].country_code, + id: updateData.geo_zones[0].id, + type: updateData.geo_zones[0].type, + country_code: updateData.geo_zones[0].country_code, }), ]), }) ) - ++i - } - }) + }) - it("should fail on duplicated service zone name", async function () { - const createData = [ - { - name: "service-zone-test", - geo_zones: [ - { - type: GeoZoneType.COUNTRY, - country_code: "fr", - }, - ], - }, - { - name: "service-zone-test2", + it("should update a collection of service zones", async function () { + const createData = [ + { + name: "service-zone-test", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + ], + }, + { + name: "service-zone-test2", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "us", + }, + ], + }, + ] + + const createdServiceZones = await service.createServiceZones(createData) + + const updateData = createdServiceZones.map((serviceZone, index) => ({ + id: serviceZone.id, + name: `updated-service-zone-test${index + 1}`, geo_zones: [ { + id: serviceZone.geo_zones[0].id, type: GeoZoneType.COUNTRY, - country_code: "us", + country_code: index % 2 === 0 ? "us" : "fr", }, ], - }, - ] + })) - const createdServiceZones = await service.createServiceZones(createData) + const updatedServiceZones = await service.updateServiceZones(updateData) - const updateData = { - id: createdServiceZones[1].id, - name: "service-zone-test", // This is the name of the first service zone - geo_zones: [ + expect(updatedServiceZones).toHaveLength(2) + + let i = 0 + for (const data_ of updateData) { + expect(updatedServiceZones[i]).toEqual( + expect.objectContaining({ + id: data_.id, + name: data_.name, + geo_zones: expect.arrayContaining([ + expect.objectContaining({ + id: data_.geo_zones[0].id, + type: data_.geo_zones[0].type, + country_code: data_.geo_zones[0].country_code, + }), + ]), + }) + ) + ++i + } + }) + + it("should fail on duplicated service zone name", async function () { + const createData = [ { - id: createdServiceZones[1].geo_zones[0].id, - type: GeoZoneType.COUNTRY, - country_code: "us", + name: "service-zone-test", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + ], }, - ], - } - - const err = await service.updateServiceZones(updateData).catch((e) => e) + { + name: "service-zone-test2", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "us", + }, + ], + }, + ] - expect(err).toBeDefined() - expect(err.constraint).toBe("IDX_service_zone_name_unique") - }) - }) + const createdServiceZones = await service.createServiceZones(createData) - describe("on create geo zones", () => { - it("should create a new geo zone", async function () { - const data = { - type: GeoZoneType.COUNTRY, - country_code: "fr", - } + const updateData = { + id: createdServiceZones[1].id, + name: "service-zone-test", // This is the name of the first service zone + geo_zones: [ + { + id: createdServiceZones[1].geo_zones[0].id, + type: GeoZoneType.COUNTRY, + country_code: "us", + }, + ], + } - const geoZone = await service.createGeoZones(data) + const err = await service.updateServiceZones(updateData).catch((e) => e) - expect(geoZone).toEqual( - expect.objectContaining({ - id: expect.any(String), - type: data.type, - country_code: data.country_code, - }) - ) + expect(err).toBeDefined() + expect(err.constraint).toBe("IDX_service_zone_name_unique") + }) }) - it("should create a collection of geo zones", async function () { - const data = [ - { + describe("on create geo zones", () => { + it("should create a new geo zone", async function () { + const data = { type: GeoZoneType.COUNTRY, country_code: "fr", - }, - { - type: GeoZoneType.COUNTRY, - country_code: "us", - }, - ] - - const geoZones = await service.createGeoZones(data) + } - expect(geoZones).toHaveLength(2) + const geoZone = await service.createGeoZones(data) - let i = 0 - for (const data_ of data) { - expect(geoZones[i]).toEqual( + expect(geoZone).toEqual( expect.objectContaining({ id: expect.any(String), - type: data_.type, - country_code: data_.country_code, + type: data.type, + country_code: data.country_code, }) ) - ++i - } - }) - }) + }) - describe("on update geo zones", () => { - it("should update an existing geo zone", async function () { - const createData = { - type: GeoZoneType.COUNTRY, - country_code: "fr", - } - - const createdGeoZone = await service.createGeoZones(createData) + it("should create a collection of geo zones", async function () { + const data = [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + { + type: GeoZoneType.COUNTRY, + country_code: "us", + }, + ] - const updateData = { - id: createdGeoZone.id, - type: GeoZoneType.COUNTRY, - country_code: "us", - } + const geoZones = await service.createGeoZones(data) - const updatedGeoZone = await service.updateGeoZones(updateData) + expect(geoZones).toHaveLength(2) - expect(updatedGeoZone).toEqual( - expect.objectContaining({ - id: updateData.id, - type: updateData.type, - country_code: updateData.country_code, - }) - ) + let i = 0 + for (const data_ of data) { + expect(geoZones[i]).toEqual( + expect.objectContaining({ + id: expect.any(String), + type: data_.type, + country_code: data_.country_code, + }) + ) + ++i + } + }) }) - it("should update a collection of geo zones", async function () { - const createData = [ - { + describe("on update geo zones", () => { + it("should update an existing geo zone", async function () { + const createData = { type: GeoZoneType.COUNTRY, country_code: "fr", - }, - { - type: GeoZoneType.COUNTRY, - country_code: "us", - }, - ] - - const createdGeoZones = await service.createGeoZones(createData) + } - const updateData = createdGeoZones.map((geoZone, index) => ({ - id: geoZone.id, - type: GeoZoneType.COUNTRY, - country_code: index % 2 === 0 ? "us" : "fr", - })) + const createdGeoZone = await service.createGeoZones(createData) - const updatedGeoZones = await service.updateGeoZones(updateData) + const updateData = { + id: createdGeoZone.id, + type: GeoZoneType.COUNTRY, + country_code: "us", + } - expect(updatedGeoZones).toHaveLength(2) + const updatedGeoZone = await service.updateGeoZones(updateData) - let i = 0 - for (const data_ of updateData) { - expect(updatedGeoZones[i]).toEqual( + expect(updatedGeoZone).toEqual( expect.objectContaining({ - id: data_.id, - type: data_.type, - country_code: data_.country_code, + id: updateData.id, + type: updateData.type, + country_code: updateData.country_code, }) ) - ++i - } + }) + + it("should update a collection of geo zones", async function () { + const createData = [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + { + type: GeoZoneType.COUNTRY, + country_code: "us", + }, + ] + + const createdGeoZones = await service.createGeoZones(createData) + + const updateData = createdGeoZones.map((geoZone, index) => ({ + id: geoZone.id, + type: GeoZoneType.COUNTRY, + country_code: index % 2 === 0 ? "us" : "fr", + })) + + const updatedGeoZones = await service.updateGeoZones(updateData) + + expect(updatedGeoZones).toHaveLength(2) + + let i = 0 + for (const data_ of updateData) { + expect(updatedGeoZones[i]).toEqual( + expect.objectContaining({ + id: data_.id, + type: data_.type, + country_code: data_.country_code, + }) + ) + ++i + } + }) }) }) }) From 7053900d28f6bd9a1adcc2476f477857316e17b1 Mon Sep 17 00:00:00 2001 From: adrien2p Date: Tue, 13 Feb 2024 15:30:35 +0100 Subject: [PATCH 21/29] tackle all type issues or missing in the integration tesst --- .../fulfillment-module-service.spec.ts | 109 ++++++++++-------- 1 file changed, 61 insertions(+), 48 deletions(-) diff --git a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts index e3a22217e9a2b..98629c08ca466 100644 --- a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts +++ b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts @@ -2,7 +2,13 @@ import { Modules } from "@medusajs/modules-sdk" import { initModules } from "medusa-test-utils/dist" import { CreateFulfillmentSetDTO, + CreateGeoZoneDTO, + CreateServiceZoneDTO, + GeoZoneDTO, IFulfillmentModuleService, + ServiceZoneDTO, + UpdateFulfillmentSetDTO, + UpdateGeoZoneDTO, } from "@medusajs/types" import { getInitModuleConfig, MikroOrmWrapper } from "../utils" import { GeoZoneType } from "@medusajs/utils" @@ -472,7 +478,7 @@ describe("fulfillment module service", function () { describe("on create service zones", () => { it("should create a new service zone", async function () { - const data = { + const data: CreateServiceZoneDTO = { name: "test", geo_zones: [ { @@ -490,8 +496,8 @@ describe("fulfillment module service", function () { name: data.name, geo_zones: expect.arrayContaining([ expect.objectContaining({ - type: data.geo_zones[0].type, - country_code: data.geo_zones[0].country_code, + type: (data.geo_zones![0] as GeoZoneDTO).type, + country_code: (data.geo_zones![0] as GeoZoneDTO).country_code, }), ]), }) @@ -499,7 +505,7 @@ describe("fulfillment module service", function () { }) it("should create a collection of service zones", async function () { - const data = [ + const data: CreateServiceZoneDTO[] = [ { name: "test", geo_zones: [ @@ -541,8 +547,9 @@ describe("fulfillment module service", function () { name: data_.name, geo_zones: expect.arrayContaining([ expect.objectContaining({ - type: data_.geo_zones[0].type, - country_code: data_.geo_zones[0].country_code, + type: (data_.geo_zones![0] as GeoZoneDTO).type, + country_code: (data_.geo_zones![0] as GeoZoneDTO) + .country_code, }), ]), }) @@ -557,7 +564,7 @@ describe("fulfillment module service", function () { }) it("should fail on duplicated service zone name", async function () { - const data = { + const data: CreateServiceZoneDTO = { name: "test", geo_zones: [ { @@ -659,7 +666,7 @@ describe("fulfillment module service", function () { const createdFulfillmentSet = await service.create(createData) - const createServiceZoneData = { + const createServiceZoneData: CreateServiceZoneDTO = { name: "service-zone-test2", geo_zones: [ { @@ -669,7 +676,7 @@ describe("fulfillment module service", function () { ], } - const updateData = { + const updateData: UpdateFulfillmentSetDTO = { id: createdFulfillmentSet.id, name: "updated-test", type: "updated-test-type", @@ -686,13 +693,15 @@ describe("fulfillment module service", function () { service_zones: expect.arrayContaining([ expect.objectContaining({ id: expect.any(String), - name: updateData.service_zones[0].name, + name: (updateData.service_zones![0] as ServiceZoneDTO).name, geo_zones: expect.arrayContaining([ expect.objectContaining({ id: expect.any(String), - type: updateData.service_zones[0].geo_zones[0].type, - country_code: - updateData.service_zones[0].geo_zones[0].country_code, + type: (updateData.service_zones![0] as ServiceZoneDTO) + .geo_zones[0].type, + country_code: ( + updateData.service_zones![0] as ServiceZoneDTO + ).geo_zones[0].country_code, }), ]), }), @@ -720,7 +729,7 @@ describe("fulfillment module service", function () { const createdFulfillmentSet = await service.create(createData) - const createServiceZoneData = { + const createServiceZoneData: CreateServiceZoneDTO = { name: "service-zone-test2", geo_zones: [ { @@ -730,7 +739,7 @@ describe("fulfillment module service", function () { ], } - const updateData = { + const updateData: UpdateFulfillmentSetDTO = { id: createdFulfillmentSet.id, name: "updated-test", type: "updated-test-type", @@ -753,13 +762,15 @@ describe("fulfillment module service", function () { }), expect.objectContaining({ id: expect.any(String), - name: updateData.service_zones[1].name, + name: (updateData.service_zones![1] as ServiceZoneDTO).name, geo_zones: expect.arrayContaining([ expect.objectContaining({ id: expect.any(String), - type: updateData.service_zones[1].geo_zones[0].type, - country_code: - updateData.service_zones[1].geo_zones[0].country_code, + type: (updateData.service_zones![1] as ServiceZoneDTO) + .geo_zones[0].type, + country_code: ( + updateData.service_zones![1] as ServiceZoneDTO + ).geo_zones[0].country_code, }), ]), }), @@ -830,8 +841,8 @@ describe("fulfillment module service", function () { const createdFulfillmentSets = await service.create(createData) - const updateData = createdFulfillmentSets.map( - (fulfillmentSet, index) => ({ + const updateData: UpdateFulfillmentSetDTO[] = + createdFulfillmentSets.map((fulfillmentSet, index) => ({ id: fulfillmentSet.id, name: `updated-test${index + 1}`, type: `updated-test-type${index + 1}`, @@ -846,8 +857,7 @@ describe("fulfillment module service", function () { ], }, ], - }) - ) + })) const updatedFulfillmentSets = await service.update(updateData) @@ -863,13 +873,14 @@ describe("fulfillment module service", function () { service_zones: expect.arrayContaining([ expect.objectContaining({ id: expect.any(String), - name: data_.service_zones[0].name, + name: (data_.service_zones![0] as ServiceZoneDTO).name, geo_zones: expect.arrayContaining([ expect.objectContaining({ id: expect.any(String), - type: data_.service_zones[0].geo_zones[0].type, - country_code: - data_.service_zones[0].geo_zones[0].country_code, + type: (data_.service_zones![0] as ServiceZoneDTO) + .geo_zones[0].type, + country_code: (data_.service_zones![0] as ServiceZoneDTO) + .geo_zones[0].country_code, }), ]), }), @@ -916,8 +927,8 @@ describe("fulfillment module service", function () { const createdFulfillmentSets = await service.create(createData) - const updateData = createdFulfillmentSets.map( - (fulfillmentSet, index) => ({ + const updateData: UpdateFulfillmentSetDTO[] = + createdFulfillmentSets.map((fulfillmentSet, index) => ({ id: fulfillmentSet.id, name: `updated-test${index + 1}`, type: `updated-test-type${index + 1}`, @@ -933,8 +944,7 @@ describe("fulfillment module service", function () { ], }, ], - }) - ) + })) const updatedFulfillmentSets = await service.update(updateData) @@ -953,13 +963,14 @@ describe("fulfillment module service", function () { }), expect.objectContaining({ id: expect.any(String), - name: data_.service_zones[1].name, + name: (data_.service_zones![1] as ServiceZoneDTO).name, geo_zones: expect.arrayContaining([ expect.objectContaining({ id: expect.any(String), - type: data_.service_zones[1].geo_zones[0].type, - country_code: - data_.service_zones[1].geo_zones[0].country_code, + type: (data_.service_zones![1] as ServiceZoneDTO) + .geo_zones[0].type, + country_code: (data_.service_zones![1] as ServiceZoneDTO) + .geo_zones[0].country_code, }), ]), }), @@ -973,7 +984,7 @@ describe("fulfillment module service", function () { describe("on update service zones", () => { it("should update an existing service zone", async function () { - const createData = { + const createData: CreateServiceZoneDTO = { name: "service-zone-test", geo_zones: [ { @@ -1015,7 +1026,7 @@ describe("fulfillment module service", function () { }) it("should update a collection of service zones", async function () { - const createData = [ + const createData: CreateServiceZoneDTO[] = [ { name: "service-zone-test", geo_zones: [ @@ -1074,7 +1085,7 @@ describe("fulfillment module service", function () { }) it("should fail on duplicated service zone name", async function () { - const createData = [ + const createData: CreateServiceZoneDTO[] = [ { name: "service-zone-test", geo_zones: [ @@ -1118,7 +1129,7 @@ describe("fulfillment module service", function () { describe("on create geo zones", () => { it("should create a new geo zone", async function () { - const data = { + const data: CreateGeoZoneDTO = { type: GeoZoneType.COUNTRY, country_code: "fr", } @@ -1135,7 +1146,7 @@ describe("fulfillment module service", function () { }) it("should create a collection of geo zones", async function () { - const data = [ + const data: CreateGeoZoneDTO[] = [ { type: GeoZoneType.COUNTRY, country_code: "fr", @@ -1166,14 +1177,14 @@ describe("fulfillment module service", function () { describe("on update geo zones", () => { it("should update an existing geo zone", async function () { - const createData = { + const createData: CreateGeoZoneDTO = { type: GeoZoneType.COUNTRY, country_code: "fr", } const createdGeoZone = await service.createGeoZones(createData) - const updateData = { + const updateData: UpdateGeoZoneDTO = { id: createdGeoZone.id, type: GeoZoneType.COUNTRY, country_code: "us", @@ -1191,7 +1202,7 @@ describe("fulfillment module service", function () { }) it("should update a collection of geo zones", async function () { - const createData = [ + const createData: CreateGeoZoneDTO[] = [ { type: GeoZoneType.COUNTRY, country_code: "fr", @@ -1204,11 +1215,13 @@ describe("fulfillment module service", function () { const createdGeoZones = await service.createGeoZones(createData) - const updateData = createdGeoZones.map((geoZone, index) => ({ - id: geoZone.id, - type: GeoZoneType.COUNTRY, - country_code: index % 2 === 0 ? "us" : "fr", - })) + const updateData: UpdateGeoZoneDTO[] = createdGeoZones.map( + (geoZone, index) => ({ + id: geoZone.id, + type: GeoZoneType.COUNTRY, + country_code: index % 2 === 0 ? "us" : "fr", + }) + ) const updatedGeoZones = await service.updateGeoZones(updateData) From f69b91bba6a6d2af59a6a00e4fbbf396f08206bd Mon Sep 17 00:00:00 2001 From: adrien2p Date: Tue, 13 Feb 2024 16:31:19 +0100 Subject: [PATCH 22/29] add more tests --- .../fulfillment-module-service.spec.ts | 170 +++++++++++++----- 1 file changed, 122 insertions(+), 48 deletions(-) diff --git a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts index 98629c08ca466..cee6ceafc237d 100644 --- a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts +++ b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts @@ -4,6 +4,7 @@ import { CreateFulfillmentSetDTO, CreateGeoZoneDTO, CreateServiceZoneDTO, + FulfillmentSetDTO, GeoZoneDTO, IFulfillmentModuleService, ServiceZoneDTO, @@ -582,6 +583,54 @@ describe("fulfillment module service", function () { }) }) + describe("on create geo zones", () => { + it("should create a new geo zone", async function () { + const data: CreateGeoZoneDTO = { + type: GeoZoneType.COUNTRY, + country_code: "fr", + } + + const geoZone = await service.createGeoZones(data) + + expect(geoZone).toEqual( + expect.objectContaining({ + id: expect.any(String), + type: data.type, + country_code: data.country_code, + }) + ) + }) + + it("should create a collection of geo zones", async function () { + const data: CreateGeoZoneDTO[] = [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + { + type: GeoZoneType.COUNTRY, + country_code: "us", + }, + ] + + const geoZones = await service.createGeoZones(data) + + expect(geoZones).toHaveLength(2) + + let i = 0 + for (const data_ of data) { + expect(geoZones[i]).toEqual( + expect.objectContaining({ + id: expect.any(String), + type: data_.type, + country_code: data_.country_code, + }) + ) + ++i + } + }) + }) + describe("on update", () => { it("should update an existing fulfillment set", async function () { const createData: CreateFulfillmentSetDTO = { @@ -1127,54 +1176,6 @@ describe("fulfillment module service", function () { }) }) - describe("on create geo zones", () => { - it("should create a new geo zone", async function () { - const data: CreateGeoZoneDTO = { - type: GeoZoneType.COUNTRY, - country_code: "fr", - } - - const geoZone = await service.createGeoZones(data) - - expect(geoZone).toEqual( - expect.objectContaining({ - id: expect.any(String), - type: data.type, - country_code: data.country_code, - }) - ) - }) - - it("should create a collection of geo zones", async function () { - const data: CreateGeoZoneDTO[] = [ - { - type: GeoZoneType.COUNTRY, - country_code: "fr", - }, - { - type: GeoZoneType.COUNTRY, - country_code: "us", - }, - ] - - const geoZones = await service.createGeoZones(data) - - expect(geoZones).toHaveLength(2) - - let i = 0 - for (const data_ of data) { - expect(geoZones[i]).toEqual( - expect.objectContaining({ - id: expect.any(String), - type: data_.type, - country_code: data_.country_code, - }) - ) - ++i - } - }) - }) - describe("on update geo zones", () => { it("should update an existing geo zone", async function () { const createData: CreateGeoZoneDTO = { @@ -1240,5 +1241,78 @@ describe("fulfillment module service", function () { } }) }) + + describe("on delete", () => { + it("should delete a fulfillment set", async function () { + const createdSet = await service.create({ + name: "test", + type: "test-type", + service_zones: [ + { + name: "test", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + ], + }, + ], + }) + + await service.delete(createdSet.id) + + let retrievedSet: FulfillmentSetDTO | undefined + await service + .retrieve(createdSet.id) + .then((set) => (retrievedSet = set)) + .catch((e) => e) + + expect(retrievedSet).toBeUndefined() + }) + }) + + describe("on delete service zones", () => { + it("should delete a service zone", async function () { + const createdZone = await service.createServiceZones({ + name: "test", + geo_zones: [ + { + type: GeoZoneType.COUNTRY, + country_code: "fr", + }, + ], + }) + + await service.deleteServiceZones(createdZone.id) + + let retrievedZone: ServiceZoneDTO | undefined + await service + .retrieveServiceZone(createdZone.id) + .then((zone) => (retrievedZone = zone)) + .catch((e) => e) + + expect(retrievedZone).toBeUndefined() + }) + }) + + describe("on delete geo zones", () => { + it("should delete a geo zone", async function () { + const createdZone = await service.createGeoZones({ + type: GeoZoneType.COUNTRY, + country_code: "fr", + }) + + await service.deleteGeoZones(createdZone.id) + + let retrievedZone: GeoZoneDTO | undefined + await service + .retrieveGeoZone(createdZone.id) + .then((zone) => (retrievedZone = zone)) + .catch((e) => e) + + expect(retrievedZone).toBeUndefined() + }) + }) }) }) From 9970652d09bbb54367aa69796f6b8602e7798cd2 Mon Sep 17 00:00:00 2001 From: Adrien de Peretti Date: Tue, 13 Feb 2024 16:39:13 +0100 Subject: [PATCH 23/29] Create nine-nails-end.md --- .changeset/nine-nails-end.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/nine-nails-end.md diff --git a/.changeset/nine-nails-end.md b/.changeset/nine-nails-end.md new file mode 100644 index 0000000000000..36ea7dcd0f17b --- /dev/null +++ b/.changeset/nine-nails-end.md @@ -0,0 +1,5 @@ +--- +"@medusajs/types": patch +--- + +feat(fulfillment): Module service implementation first iteration From 0e7f690701fb2f4a6edc8cfbcc5b9bdb35b581dd Mon Sep 17 00:00:00 2001 From: adrien2p Date: Tue, 13 Feb 2024 17:47:11 +0100 Subject: [PATCH 24/29] Add fix ordered on many to many + add pivot table orphan data check --- .../fulfillment-module-service.spec.ts | 59 +++++++++++++++++++ .../.snapshot-medusa-fulfillment.json | 32 +++++++--- ...12155252.ts => Migration20240213164003.ts} | 10 ++-- .../fulfillment/src/models/fullfilment-set.ts | 1 + .../fulfillment/src/models/service-zone.ts | 1 + 5 files changed, 90 insertions(+), 13 deletions(-) rename packages/fulfillment/src/migrations/{Migration20240212155252.ts => Migration20240213164003.ts} (95%) diff --git a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts index cee6ceafc237d..cb26ab2eafef2 100644 --- a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts +++ b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts @@ -757,6 +757,19 @@ describe("fulfillment module service", function () { ]), }) ) + + // Validate pivot table no oprhan data left + const pivotTableData = await MikroOrmWrapper.forkManager().execute( + "SELECT * from fulfillment_set_service_zones" + ) + + expect(pivotTableData).toHaveLength(1) + expect(pivotTableData[0]).toEqual( + expect.objectContaining({ + fulfillment_set_id: createdFulfillmentSet.id, + service_zone_id: updatedFulfillmentSet.service_zones[0].id, + }) + ) }) it("should update an existing fulfillment set and add a new service zone", async function () { @@ -938,6 +951,25 @@ describe("fulfillment module service", function () { ) ++i } + + // Validate pivot table no oprhan data left + const pivotTableData = await MikroOrmWrapper.forkManager().execute( + "SELECT * from fulfillment_set_service_zones" + ) + + expect(pivotTableData).toHaveLength(2) + expect(pivotTableData).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + fulfillment_set_id: updatedFulfillmentSets[0].id, + service_zone_id: updatedFulfillmentSets[0].service_zones[0].id, + }), + expect.objectContaining({ + fulfillment_set_id: updatedFulfillmentSets[1].id, + service_zone_id: updatedFulfillmentSets[1].service_zones[0].id, + }), + ]) + ) }) it("should update a collection of fulfillment sets and add new service zones", async function () { @@ -1028,6 +1060,33 @@ describe("fulfillment module service", function () { ) ++i } + + // Validate pivot table no oprhan data left + const pivotTableData = await MikroOrmWrapper.forkManager().execute( + "SELECT * from fulfillment_set_service_zones" + ) + + expect(pivotTableData).toHaveLength(4) + expect(pivotTableData).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + fulfillment_set_id: updatedFulfillmentSets[0].id, + service_zone_id: updatedFulfillmentSets[0].service_zones[0].id, + }), + expect.objectContaining({ + fulfillment_set_id: updatedFulfillmentSets[1].id, + service_zone_id: updatedFulfillmentSets[1].service_zones[0].id, + }), + expect.objectContaining({ + fulfillment_set_id: updatedFulfillmentSets[0].id, + service_zone_id: updatedFulfillmentSets[0].service_zones[1].id, + }), + expect.objectContaining({ + fulfillment_set_id: updatedFulfillmentSets[1].id, + service_zone_id: updatedFulfillmentSets[1].service_zones[1].id, + }), + ]) + ) }) }) diff --git a/packages/fulfillment/src/migrations/.snapshot-medusa-fulfillment.json b/packages/fulfillment/src/migrations/.snapshot-medusa-fulfillment.json index b4ab9e2b4651f..72ea112755cdf 100644 --- a/packages/fulfillment/src/migrations/.snapshot-medusa-fulfillment.json +++ b/packages/fulfillment/src/migrations/.snapshot-medusa-fulfillment.json @@ -274,7 +274,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "ALTER TABLE IF EXISTS \"fulfillment_set\" ADD CONSTRAINT \"IDX_fulfillment_set_name_unique\" UNIQUE (name)" + "expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_fulfillment_set_name_unique\" ON \"fulfillment_set\" (name) WHERE deleted_at IS NOT NULL" }, { "keyName": "IDX_fulfillment_set_deleted_at", @@ -612,7 +612,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "ALTER TABLE IF EXISTS \"service_zone\" ADD CONSTRAINT \"IDX_service_zone_name_unique\" UNIQUE (name)" + "expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_service_zone_name_unique\" ON \"service_zone\" (name) WHERE deleted_at IS NOT NULL" }, { "keyName": "IDX_service_zone_deleted_at", @@ -639,6 +639,15 @@ }, { "columns": { + "id": { + "name": "id", + "type": "serial", + "unsigned": true, + "autoincrement": true, + "primary": true, + "nullable": false, + "mappedType": "integer" + }, "service_zone_id": { "name": "service_zone_id", "type": "text", @@ -664,10 +673,9 @@ { "keyName": "service_zone_geo_zones_pkey", "columnNames": [ - "service_zone_id", - "geo_zone_id" + "id" ], - "composite": true, + "composite": false, "primary": true, "unique": true } @@ -704,6 +712,15 @@ }, { "columns": { + "id": { + "name": "id", + "type": "serial", + "unsigned": true, + "autoincrement": true, + "primary": true, + "nullable": false, + "mappedType": "integer" + }, "fulfillment_set_id": { "name": "fulfillment_set_id", "type": "text", @@ -729,10 +746,9 @@ { "keyName": "fulfillment_set_service_zones_pkey", "columnNames": [ - "fulfillment_set_id", - "service_zone_id" + "id" ], - "composite": true, + "composite": false, "primary": true, "unique": true } diff --git a/packages/fulfillment/src/migrations/Migration20240212155252.ts b/packages/fulfillment/src/migrations/Migration20240213164003.ts similarity index 95% rename from packages/fulfillment/src/migrations/Migration20240212155252.ts rename to packages/fulfillment/src/migrations/Migration20240213164003.ts index dde6a90b814d4..7eca943d97ffd 100644 --- a/packages/fulfillment/src/migrations/Migration20240212155252.ts +++ b/packages/fulfillment/src/migrations/Migration20240213164003.ts @@ -1,6 +1,6 @@ import { Migration } from '@mikro-orm/migrations'; -export class Migration20240212155252 extends Migration { +export class Migration20240213164003 extends Migration { async up(): Promise { this.addSql('create table if not exists "fulfillment_address" ("id" text not null, "fulfillment_id" text 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"));'); @@ -8,7 +8,7 @@ export class Migration20240212155252 extends Migration { 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_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('ALTER TABLE IF EXISTS "fulfillment_set" ADD CONSTRAINT "IDX_fulfillment_set_name_unique" UNIQUE (name);'); + this.addSql('CREATE UNIQUE INDEX IF NOT EXISTS "IDX_fulfillment_set_name_unique" ON "fulfillment_set" (name) WHERE deleted_at IS NOT 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 "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, "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"));'); @@ -21,12 +21,12 @@ export class Migration20240212155252 extends Migration { this.addSql('CREATE INDEX IF NOT EXISTS "IDX_service_provider_deleted_at" ON "service_provider" (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, "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('ALTER TABLE IF EXISTS "service_zone" ADD CONSTRAINT "IDX_service_zone_name_unique" UNIQUE (name);'); + this.addSql('CREATE UNIQUE INDEX IF NOT EXISTS "IDX_service_zone_name_unique" ON "service_zone" (name) WHERE deleted_at IS NOT 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 "service_zone_geo_zones" ("service_zone_id" text not null, "geo_zone_id" text not null, constraint "service_zone_geo_zones_pkey" primary key ("service_zone_id", "geo_zone_id"));'); + this.addSql('create table if not exists "service_zone_geo_zones" ("id" serial primary key, "service_zone_id" text not null, "geo_zone_id" text not null);'); - this.addSql('create table if not exists "fulfillment_set_service_zones" ("fulfillment_set_id" text not null, "service_zone_id" text not null, constraint "fulfillment_set_service_zones_pkey" primary key ("fulfillment_set_id", "service_zone_id"));'); + this.addSql('create table if not exists "fulfillment_set_service_zones" ("id" serial primary key, "fulfillment_set_id" text not null, "service_zone_id" text 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, "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_type_pkey" primary key ("id"));'); this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_type_shipping_option_id" ON "shipping_option_type" (shipping_option_id) WHERE deleted_at IS NULL;'); diff --git a/packages/fulfillment/src/models/fullfilment-set.ts b/packages/fulfillment/src/models/fullfilment-set.ts index 35e22fbe151e4..9027836b7d3bf 100644 --- a/packages/fulfillment/src/models/fullfilment-set.ts +++ b/packages/fulfillment/src/models/fullfilment-set.ts @@ -64,6 +64,7 @@ export default class FulfillmentSet { pivotTable: "fulfillment_set_service_zones", joinColumn: "fulfillment_set_id", inverseJoinColumn: "service_zone_id", + fixedOrder: true, }) service_zones = new Collection(this) diff --git a/packages/fulfillment/src/models/service-zone.ts b/packages/fulfillment/src/models/service-zone.ts index 9a249f0a1fc24..0f731c84195a7 100644 --- a/packages/fulfillment/src/models/service-zone.ts +++ b/packages/fulfillment/src/models/service-zone.ts @@ -70,6 +70,7 @@ export default class ServiceZone { pivotTable: "service_zone_geo_zones", joinColumn: "service_zone_id", inverseJoinColumn: "geo_zone_id", + fixedOrder: true, }) geo_zones = new Collection(this) From 7174f73e309a88d4ad5a73449e74eabebf4c2987 Mon Sep 17 00:00:00 2001 From: adrien2p Date: Tue, 13 Feb 2024 17:56:04 +0100 Subject: [PATCH 25/29] fix unique indexes --- .../src/migrations/.snapshot-medusa-fulfillment.json | 4 ++-- ...igration20240213164003.ts => Migration20240213165503.ts} | 6 +++--- packages/fulfillment/src/models/fullfilment-set.ts | 2 +- packages/fulfillment/src/models/service-zone.ts | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) rename packages/fulfillment/src/migrations/{Migration20240213164003.ts => Migration20240213165503.ts} (99%) diff --git a/packages/fulfillment/src/migrations/.snapshot-medusa-fulfillment.json b/packages/fulfillment/src/migrations/.snapshot-medusa-fulfillment.json index 72ea112755cdf..9d06e91583fdf 100644 --- a/packages/fulfillment/src/migrations/.snapshot-medusa-fulfillment.json +++ b/packages/fulfillment/src/migrations/.snapshot-medusa-fulfillment.json @@ -274,7 +274,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_fulfillment_set_name_unique\" ON \"fulfillment_set\" (name) WHERE deleted_at IS NOT NULL" + "expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_fulfillment_set_name_unique\" ON \"fulfillment_set\" (name) WHERE deleted_at IS NULL" }, { "keyName": "IDX_fulfillment_set_deleted_at", @@ -612,7 +612,7 @@ "composite": false, "primary": false, "unique": false, - "expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_service_zone_name_unique\" ON \"service_zone\" (name) WHERE deleted_at IS NOT NULL" + "expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_service_zone_name_unique\" ON \"service_zone\" (name) WHERE deleted_at IS NULL" }, { "keyName": "IDX_service_zone_deleted_at", diff --git a/packages/fulfillment/src/migrations/Migration20240213164003.ts b/packages/fulfillment/src/migrations/Migration20240213165503.ts similarity index 99% rename from packages/fulfillment/src/migrations/Migration20240213164003.ts rename to packages/fulfillment/src/migrations/Migration20240213165503.ts index 7eca943d97ffd..2058174b0ae9f 100644 --- a/packages/fulfillment/src/migrations/Migration20240213164003.ts +++ b/packages/fulfillment/src/migrations/Migration20240213165503.ts @@ -1,6 +1,6 @@ import { Migration } from '@mikro-orm/migrations'; -export class Migration20240213164003 extends Migration { +export class Migration20240213165503 extends Migration { async up(): Promise { this.addSql('create table if not exists "fulfillment_address" ("id" text not null, "fulfillment_id" text 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"));'); @@ -8,7 +8,7 @@ export class Migration20240213164003 extends Migration { 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_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 NOT NULL;'); + 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 "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, "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"));'); @@ -21,7 +21,7 @@ export class Migration20240213164003 extends Migration { this.addSql('CREATE INDEX IF NOT EXISTS "IDX_service_provider_deleted_at" ON "service_provider" (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, "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 NOT NULL;'); + 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_deleted_at" ON "service_zone" (deleted_at) WHERE deleted_at IS NOT NULL;'); this.addSql('create table if not exists "service_zone_geo_zones" ("id" serial primary key, "service_zone_id" text not null, "geo_zone_id" text not null);'); diff --git a/packages/fulfillment/src/models/fullfilment-set.ts b/packages/fulfillment/src/models/fullfilment-set.ts index 9027836b7d3bf..bc3a58c8261b5 100644 --- a/packages/fulfillment/src/models/fullfilment-set.ts +++ b/packages/fulfillment/src/models/fullfilment-set.ts @@ -35,7 +35,7 @@ const nameIndexStatement = createPsqlIndexStatementHelper({ tableName: "fulfillment_set", columns: "name", unique: true, - where: "deleted_at IS NOT NULL", + where: "deleted_at IS NULL", }) @Entity() diff --git a/packages/fulfillment/src/models/service-zone.ts b/packages/fulfillment/src/models/service-zone.ts index 0f731c84195a7..c7012b4819e6d 100644 --- a/packages/fulfillment/src/models/service-zone.ts +++ b/packages/fulfillment/src/models/service-zone.ts @@ -38,7 +38,7 @@ const nameIndexStatement = createPsqlIndexStatementHelper({ tableName: "service_zone", columns: "name", unique: true, - where: "deleted_at IS NOT NULL", + where: "deleted_at IS NULL", }) @Entity() From f1f3c509efbfbad60cc2d2752d6a69bd1d51dd50 Mon Sep 17 00:00:00 2001 From: adrien2p Date: Wed, 14 Feb 2024 11:39:20 +0100 Subject: [PATCH 26/29] Update relationship and create* methods --- .../fulfillment-module-service.spec.ts | 56 ++- .../.snapshot-medusa-fulfillment.json | 362 +++++++----------- ...13165503.ts => Migration20240214103108.ts} | 26 +- .../fulfillment/src/models/fullfilment-set.ts | 11 +- packages/fulfillment/src/models/geo-zone.ts | 22 +- .../fulfillment/src/models/service-zone.ts | 35 +- .../services/fulfillment-module-service.ts | 279 ++------------ .../fulfillment/mutations/fulfillment-set.ts | 4 +- .../src/fulfillment/mutations/geo-zone.ts | 9 +- .../src/fulfillment/mutations/service-zone.ts | 5 +- 10 files changed, 268 insertions(+), 541 deletions(-) rename packages/fulfillment/src/migrations/{Migration20240213165503.ts => Migration20240214103108.ts} (86%) diff --git a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts index cb26ab2eafef2..debfca75cb782 100644 --- a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts +++ b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts @@ -330,11 +330,6 @@ describe("fulfillment module service", function () { ) ++i } - - // expect the first and second fulfillment set to have the same service zone - expect(fulfillmentSets[0].service_zones[0].id).toEqual( - fulfillmentSets[1].service_zones[0].id - ) }) it("should create a new fulfillment set with new service zones and new geo zones", async function () { @@ -456,11 +451,6 @@ describe("fulfillment module service", function () { ) ++i } - - // expect the first and second fulfillment set to have the same geo zone for their service zone - expect(fulfillmentSets[0].service_zones[0].geo_zones[0].id).toEqual( - fulfillmentSets[1].service_zones[0].geo_zones[0].id - ) }) it(`should fail on duplicated fulfillment set name`, async function () { @@ -479,8 +469,14 @@ describe("fulfillment module service", function () { describe("on create service zones", () => { it("should create a new service zone", async function () { + const fulfillmentSet = await service.create({ + name: "test", + type: "test-type", + }) + const data: CreateServiceZoneDTO = { name: "test", + fulfillment_set_id: fulfillmentSet.id, geo_zones: [ { type: GeoZoneType.COUNTRY, @@ -506,9 +502,15 @@ describe("fulfillment module service", function () { }) it("should create a collection of service zones", async function () { + const fulfillmentSet = await service.create({ + name: "test", + type: "test-type", + }) + const data: CreateServiceZoneDTO[] = [ { name: "test", + fulfillment_set_id: fulfillmentSet.id, geo_zones: [ { type: GeoZoneType.COUNTRY, @@ -518,6 +520,7 @@ describe("fulfillment module service", function () { }, { name: "test2", + fulfillment_set_id: fulfillmentSet.id, geo_zones: [ { type: GeoZoneType.COUNTRY, @@ -527,6 +530,7 @@ describe("fulfillment module service", function () { }, { name: "test3", + fulfillment_set_id: fulfillmentSet.id, geo_zones: [ { type: GeoZoneType.COUNTRY, @@ -557,16 +561,17 @@ describe("fulfillment module service", function () { ) ++i } - - // expect the first and second service zone to have the same geo zone - expect(serviceZones[0].geo_zones[0].id).toEqual( - serviceZones[1].geo_zones[0].id - ) }) it("should fail on duplicated service zone name", async function () { + const fulfillmentSet = await service.create({ + name: "test", + type: "test-type", + }) + const data: CreateServiceZoneDTO = { name: "test", + fulfillment_set_id: fulfillmentSet.id, geo_zones: [ { type: GeoZoneType.COUNTRY, @@ -585,7 +590,17 @@ describe("fulfillment module service", function () { describe("on create geo zones", () => { it("should create a new geo zone", async function () { + const fulfillmentSet = await service.create({ + name: "test", + type: "test-type", + }) + const serviceZone = await service.createServiceZones({ + name: "test", + fulfillment_set_id: fulfillmentSet.id, + }) + const data: CreateGeoZoneDTO = { + service_zone_id: serviceZone.id, type: GeoZoneType.COUNTRY, country_code: "fr", } @@ -602,12 +617,23 @@ describe("fulfillment module service", function () { }) it("should create a collection of geo zones", async function () { + const fulfillmentSet = await service.create({ + name: "test", + type: "test-type", + }) + const serviceZone = await service.createServiceZones({ + name: "test", + fulfillment_set_id: fulfillmentSet.id, + }) + const data: CreateGeoZoneDTO[] = [ { + service_zone_id: serviceZone.id, type: GeoZoneType.COUNTRY, country_code: "fr", }, { + service_zone_id: serviceZone.id, type: GeoZoneType.COUNTRY, country_code: "us", }, diff --git a/packages/fulfillment/src/migrations/.snapshot-medusa-fulfillment.json b/packages/fulfillment/src/migrations/.snapshot-medusa-fulfillment.json index 9d06e91583fdf..f2b6dde65648d 100644 --- a/packages/fulfillment/src/migrations/.snapshot-medusa-fulfillment.json +++ b/packages/fulfillment/src/migrations/.snapshot-medusa-fulfillment.json @@ -310,58 +310,6 @@ "nullable": false, "mappedType": "text" }, - "type": { - "name": "type", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "default": "'country'", - "enumItems": [ - "country", - "province", - "city", - "zip" - ], - "mappedType": "enum" - }, - "country_code": { - "name": "country_code", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - }, - "province_code": { - "name": "province_code", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "text" - }, - "city": { - "name": "city", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "text" - }, - "postal_expression": { - "name": "postal_expression", - "type": "jsonb", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "json" - }, "metadata": { "name": "metadata", "type": "jsonb", @@ -404,51 +352,21 @@ "mappedType": "datetime" } }, - "name": "geo_zone", + "name": "service_provider", "schema": "public", "indexes": [ { - "keyName": "IDX_geo_zone_country_code", - "columnNames": [ - "country_code" - ], - "composite": false, - "primary": false, - "unique": false, - "expression": "CREATE INDEX IF NOT EXISTS \"IDX_geo_zone_country_code\" ON \"geo_zone\" (country_code) WHERE deleted_at IS NULL" - }, - { - "keyName": "IDX_geo_zone_province_code", - "columnNames": [ - "province_code" - ], - "composite": false, - "primary": false, - "unique": false, - "expression": "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" - }, - { - "keyName": "IDX_geo_zone_city", - "columnNames": [ - "city" - ], - "composite": false, - "primary": false, - "unique": false, - "expression": "CREATE INDEX IF NOT EXISTS \"IDX_geo_zone_city\" ON \"geo_zone\" (city) WHERE deleted_at IS NULL AND city IS NOT NULL" - }, - { - "keyName": "IDX_geo_zone_deleted_at", + "keyName": "IDX_service_provider_deleted_at", "columnNames": [ "deleted_at" ], "composite": false, "primary": false, "unique": false, - "expression": "CREATE INDEX IF NOT EXISTS \"IDX_geo_zone_deleted_at\" ON \"geo_zone\" (deleted_at) WHERE deleted_at IS NOT NULL" + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_service_provider_deleted_at\" ON \"service_provider\" (deleted_at) WHERE deleted_at IS NOT NULL" }, { - "keyName": "geo_zone_pkey", + "keyName": "service_provider_pkey", "columnNames": [ "id" ], @@ -471,6 +389,15 @@ "nullable": false, "mappedType": "text" }, + "name": { + "name": "name", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, "metadata": { "name": "metadata", "type": "jsonb", @@ -480,6 +407,15 @@ "nullable": true, "mappedType": "json" }, + "fulfillment_set_id": { + "name": "fulfillment_set_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, "created_at": { "name": "created_at", "type": "timestamptz", @@ -513,21 +449,41 @@ "mappedType": "datetime" } }, - "name": "service_provider", + "name": "service_zone", "schema": "public", "indexes": [ { - "keyName": "IDX_service_provider_deleted_at", + "keyName": "IDX_service_zone_name_unique", + "columnNames": [ + "name" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_service_zone_name_unique\" ON \"service_zone\" (name) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_service_zone_fulfillment_set_id", + "columnNames": [ + "fulfillment_set_id" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_service_zone_fulfillment_set_id\" ON \"service_zone\" (fulfillment_set_id) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_service_zone_deleted_at", "columnNames": [ "deleted_at" ], "composite": false, "primary": false, "unique": false, - "expression": "CREATE INDEX IF NOT EXISTS \"IDX_service_provider_deleted_at\" ON \"service_provider\" (deleted_at) WHERE deleted_at IS NOT NULL" + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_service_zone_deleted_at\" ON \"service_zone\" (deleted_at) WHERE deleted_at IS NOT NULL" }, { - "keyName": "service_provider_pkey", + "keyName": "service_zone_pkey", "columnNames": [ "id" ], @@ -537,7 +493,20 @@ } ], "checks": [], - "foreignKeys": {} + "foreignKeys": { + "service_zone_fulfillment_set_id_foreign": { + "constraintName": "service_zone_fulfillment_set_id_foreign", + "columnNames": [ + "fulfillment_set_id" + ], + "localTableName": "public.service_zone", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.fulfillment_set", + "updateRule": "cascade" + } + } }, { "columns": { @@ -550,8 +519,51 @@ "nullable": false, "mappedType": "text" }, - "name": { - "name": "name", + "type": { + "name": "type", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "'country'", + "enumItems": [ + "country", + "province", + "city", + "zip" + ], + "mappedType": "enum" + }, + "country_code": { + "name": "country_code", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "province_code": { + "name": "province_code", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "city": { + "name": "city", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "service_zone_id": { + "name": "service_zone_id", "type": "text", "unsigned": false, "autoincrement": false, @@ -559,6 +571,15 @@ "nullable": false, "mappedType": "text" }, + "postal_expression": { + "name": "postal_expression", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, "metadata": { "name": "metadata", "type": "jsonb", @@ -601,150 +622,61 @@ "mappedType": "datetime" } }, - "name": "service_zone", + "name": "geo_zone", "schema": "public", "indexes": [ { - "keyName": "IDX_service_zone_name_unique", + "keyName": "IDX_geo_zone_country_code", "columnNames": [ - "name" + "country_code" ], "composite": false, "primary": false, "unique": false, - "expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_service_zone_name_unique\" ON \"service_zone\" (name) WHERE deleted_at IS NULL" + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_geo_zone_country_code\" ON \"geo_zone\" (country_code) WHERE deleted_at IS NULL" }, { - "keyName": "IDX_service_zone_deleted_at", + "keyName": "IDX_geo_zone_province_code", "columnNames": [ - "deleted_at" + "province_code" ], "composite": false, "primary": false, "unique": false, - "expression": "CREATE INDEX IF NOT EXISTS \"IDX_service_zone_deleted_at\" ON \"service_zone\" (deleted_at) WHERE deleted_at IS NOT NULL" + "expression": "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" }, { - "keyName": "service_zone_pkey", + "keyName": "IDX_geo_zone_city", "columnNames": [ - "id" + "city" ], "composite": false, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": {} - }, - { - "columns": { - "id": { - "name": "id", - "type": "serial", - "unsigned": true, - "autoincrement": true, - "primary": true, - "nullable": false, - "mappedType": "integer" - }, - "service_zone_id": { - "name": "service_zone_id", - "type": "text", - "unsigned": false, - "autoincrement": false, "primary": false, - "nullable": false, - "mappedType": "text" + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_geo_zone_city\" ON \"geo_zone\" (city) WHERE deleted_at IS NULL AND city IS NOT NULL" }, - "geo_zone_id": { - "name": "geo_zone_id", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - } - }, - "name": "service_zone_geo_zones", - "schema": "public", - "indexes": [ { - "keyName": "service_zone_geo_zones_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "service_zone_geo_zones_service_zone_id_foreign": { - "constraintName": "service_zone_geo_zones_service_zone_id_foreign", + "keyName": "IDX_geo_zone_service_zone_id", "columnNames": [ "service_zone_id" ], - "localTableName": "public.service_zone_geo_zones", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.service_zone", - "deleteRule": "cascade", - "updateRule": "cascade" + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_geo_zone_service_zone_id\" ON \"geo_zone\" (service_zone_id) WHERE deleted_at IS NULL" }, - "service_zone_geo_zones_geo_zone_id_foreign": { - "constraintName": "service_zone_geo_zones_geo_zone_id_foreign", + { + "keyName": "IDX_geo_zone_deleted_at", "columnNames": [ - "geo_zone_id" - ], - "localTableName": "public.service_zone_geo_zones", - "referencedColumnNames": [ - "id" + "deleted_at" ], - "referencedTableName": "public.geo_zone", - "deleteRule": "cascade", - "updateRule": "cascade" - } - } - }, - { - "columns": { - "id": { - "name": "id", - "type": "serial", - "unsigned": true, - "autoincrement": true, - "primary": true, - "nullable": false, - "mappedType": "integer" - }, - "fulfillment_set_id": { - "name": "fulfillment_set_id", - "type": "text", - "unsigned": false, - "autoincrement": false, + "composite": false, "primary": false, - "nullable": false, - "mappedType": "text" + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_geo_zone_deleted_at\" ON \"geo_zone\" (deleted_at) WHERE deleted_at IS NOT NULL" }, - "service_zone_id": { - "name": "service_zone_id", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - } - }, - "name": "fulfillment_set_service_zones", - "schema": "public", - "indexes": [ { - "keyName": "fulfillment_set_service_zones_pkey", + "keyName": "geo_zone_pkey", "columnNames": [ "id" ], @@ -755,30 +687,16 @@ ], "checks": [], "foreignKeys": { - "fulfillment_set_service_zones_fulfillment_set_id_foreign": { - "constraintName": "fulfillment_set_service_zones_fulfillment_set_id_foreign", - "columnNames": [ - "fulfillment_set_id" - ], - "localTableName": "public.fulfillment_set_service_zones", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.fulfillment_set", - "deleteRule": "cascade", - "updateRule": "cascade" - }, - "fulfillment_set_service_zones_service_zone_id_foreign": { - "constraintName": "fulfillment_set_service_zones_service_zone_id_foreign", + "geo_zone_service_zone_id_foreign": { + "constraintName": "geo_zone_service_zone_id_foreign", "columnNames": [ "service_zone_id" ], - "localTableName": "public.fulfillment_set_service_zones", + "localTableName": "public.geo_zone", "referencedColumnNames": [ "id" ], "referencedTableName": "public.service_zone", - "deleteRule": "cascade", "updateRule": "cascade" } } diff --git a/packages/fulfillment/src/migrations/Migration20240213165503.ts b/packages/fulfillment/src/migrations/Migration20240214103108.ts similarity index 86% rename from packages/fulfillment/src/migrations/Migration20240213165503.ts rename to packages/fulfillment/src/migrations/Migration20240214103108.ts index 2058174b0ae9f..1a7fa50ce5e2c 100644 --- a/packages/fulfillment/src/migrations/Migration20240213165503.ts +++ b/packages/fulfillment/src/migrations/Migration20240214103108.ts @@ -1,6 +1,6 @@ import { Migration } from '@mikro-orm/migrations'; -export class Migration20240213165503 extends Migration { +export class Migration20240214103108 extends Migration { async up(): Promise { this.addSql('create table if not exists "fulfillment_address" ("id" text not null, "fulfillment_id" text 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"));'); @@ -11,22 +11,20 @@ export class Migration20240213165503 extends Migration { 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 "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, "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_deleted_at" ON "geo_zone" (deleted_at) WHERE deleted_at IS NOT NULL;'); - this.addSql('create table if not exists "service_provider" ("id" 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 "service_provider_pkey" primary key ("id"));'); this.addSql('CREATE INDEX IF NOT EXISTS "IDX_service_provider_deleted_at" ON "service_provider" (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, "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 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 "service_zone_geo_zones" ("id" serial primary key, "service_zone_id" text not null, "geo_zone_id" text not null);'); - - this.addSql('create table if not exists "fulfillment_set_service_zones" ("id" serial primary key, "fulfillment_set_id" text not null, "service_zone_id" text 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, "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_type_pkey" primary key ("id"));'); this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_type_shipping_option_id" ON "shipping_option_type" (shipping_option_id) WHERE deleted_at IS NULL;'); @@ -62,11 +60,9 @@ export class Migration20240213165503 extends Migration { 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_geo_zones" add constraint "service_zone_geo_zones_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 "service_zone_geo_zones" add constraint "service_zone_geo_zones_geo_zone_id_foreign" foreign key ("geo_zone_id") references "geo_zone" ("id") on update cascade on delete cascade;'); + 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;'); - this.addSql('alter table if exists "fulfillment_set_service_zones" add constraint "fulfillment_set_service_zones_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 "fulfillment_set_service_zones" add constraint "fulfillment_set_service_zones_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 "geo_zone" add constraint "geo_zone_service_zone_id_foreign" foreign key ("service_zone_id") references "service_zone" ("id") on update 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;'); 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;'); diff --git a/packages/fulfillment/src/models/fullfilment-set.ts b/packages/fulfillment/src/models/fullfilment-set.ts index bc3a58c8261b5..d58994bcf2579 100644 --- a/packages/fulfillment/src/models/fullfilment-set.ts +++ b/packages/fulfillment/src/models/fullfilment-set.ts @@ -6,11 +6,12 @@ import { import { BeforeCreate, + Cascade, Collection, Entity, Filter, Index, - ManyToMany, + OneToMany, OnInit, OptionalProps, PrimaryKey, @@ -59,12 +60,8 @@ export default class FulfillmentSet { @Property({ columnType: "jsonb", nullable: true }) metadata: Record | null = null - @ManyToMany(() => ServiceZone, "fulfillment_sets", { - owner: true, - pivotTable: "fulfillment_set_service_zones", - joinColumn: "fulfillment_set_id", - inverseJoinColumn: "service_zone_id", - fixedOrder: true, + @OneToMany(() => ServiceZone, "fulfillment_set", { + cascade: [Cascade.REMOVE, "soft-remove"] as any, }) service_zones = new Collection(this) diff --git a/packages/fulfillment/src/models/geo-zone.ts b/packages/fulfillment/src/models/geo-zone.ts index f7f6074a41313..a8cdc7076314c 100644 --- a/packages/fulfillment/src/models/geo-zone.ts +++ b/packages/fulfillment/src/models/geo-zone.ts @@ -7,12 +7,11 @@ import { import { BeforeCreate, - Collection, Entity, Enum, Filter, Index, - ManyToMany, + ManyToOne, OnInit, OptionalProps, PrimaryKey, @@ -55,6 +54,14 @@ const cityIndexStatement = createPsqlIndexStatementHelper({ where: "deleted_at IS NULL AND city IS NOT NULL", }) +const serviceZoneIdIndexName = "IDX_geo_zone_service_zone_id" +const serviceZoneIdStatement = createPsqlIndexStatementHelper({ + name: serviceZoneIdIndexName, + tableName: "geo_zone", + columns: "service_zone_id", + where: "deleted_at IS NULL", +}) + @Entity() @Filter(DALUtils.mikroOrmSoftDeletableFilterOptions) export default class GeoZone { @@ -87,6 +94,13 @@ export default class GeoZone { @Property({ columnType: "text", nullable: true }) city: string | null = null + @Property({ columnType: "text" }) + @Index({ + name: serviceZoneIdIndexName, + expression: serviceZoneIdStatement, + }) + service_zone_id: string + // TODO: Do we have an example or idea of what would be stored in this field? like lat/long for example? @Property({ columnType: "jsonb", nullable: true }) postal_expression: Record | null = null @@ -94,8 +108,8 @@ export default class GeoZone { @Property({ columnType: "jsonb", nullable: true }) metadata: Record | null = null - @ManyToMany(() => ServiceZone, (serviceZone) => serviceZone.geo_zones) - service_zones = new Collection(this) + @ManyToOne(() => ServiceZone, { persist: false }) + service_zone: ServiceZone @Property({ onCreate: () => new Date(), diff --git a/packages/fulfillment/src/models/service-zone.ts b/packages/fulfillment/src/models/service-zone.ts index c7012b4819e6d..8e6782be318a2 100644 --- a/packages/fulfillment/src/models/service-zone.ts +++ b/packages/fulfillment/src/models/service-zone.ts @@ -6,11 +6,12 @@ import { import { BeforeCreate, + Cascade, Collection, Entity, Filter, Index, - ManyToMany, + ManyToOne, OneToMany, OnInit, OptionalProps, @@ -41,6 +42,14 @@ const nameIndexStatement = createPsqlIndexStatementHelper({ where: "deleted_at IS NULL", }) +const fulfillmentSetIdIndexName = "IDX_service_zone_fulfillment_set_id" +const fulfillmentSetIdIndexStatement = createPsqlIndexStatementHelper({ + name: fulfillmentSetIdIndexName, + tableName: "service_zone", + columns: "fulfillment_set_id", + where: "deleted_at IS NULL", +}) + @Entity() @Filter(DALUtils.mikroOrmSoftDeletableFilterOptions) export default class ServiceZone { @@ -59,18 +68,18 @@ export default class ServiceZone { @Property({ columnType: "jsonb", nullable: true }) metadata: Record | null = null - @ManyToMany( - () => FulfillmentSet, - (fulfillmentSet) => fulfillmentSet.service_zones - ) - fulfillment_sets = new Collection(this) - - @ManyToMany(() => GeoZone, "service_zones", { - owner: true, - pivotTable: "service_zone_geo_zones", - joinColumn: "service_zone_id", - inverseJoinColumn: "geo_zone_id", - fixedOrder: true, + @Property({ columnType: "text" }) + @Index({ + name: fulfillmentSetIdIndexName, + expression: fulfillmentSetIdIndexStatement, + }) + fulfillment_set_id: string + + @ManyToOne(() => FulfillmentSet, { persist: false }) + fulfillment_set: FulfillmentSet + + @OneToMany(() => GeoZone, "service_zone", { + cascade: [Cascade.REMOVE, "soft-remove"] as any, }) geo_zones = new Collection(this) diff --git a/packages/fulfillment/src/services/fulfillment-module-service.ts b/packages/fulfillment/src/services/fulfillment-module-service.ts index f73ac360ebb02..2d7aed35ef56a 100644 --- a/packages/fulfillment/src/services/fulfillment-module-service.ts +++ b/packages/fulfillment/src/services/fulfillment-module-service.ts @@ -12,7 +12,6 @@ import { InjectManager, InjectTransactionManager, ModulesSdkUtils, - promiseAll, } from "@medusajs/utils" import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config" @@ -81,28 +80,24 @@ export default class FulfillmentModuleService< */ protected static getGeoZoneIdentifier( geoZone: Partial, - { preventIdUsage = false }: { preventIdUsage?: boolean } = {} + object?: any // todo remove ): string { - if (!preventIdUsage && "id" in geoZone) { - return geoZone.id! - } - - let identifier = ("type" in geoZone && geoZone.type) || "" + let identifier: string[] = [geoZone.type!] - if ("country_code" in geoZone && geoZone.country_code) { - identifier += geoZone.country_code + if (geoZone.country_code) { + identifier.push("cc:" + geoZone.country_code) } - if ("province_code" in geoZone && geoZone.province_code) { - identifier += geoZone.province_code + if (geoZone.province_code) { + identifier.push("pc:" + geoZone.province_code) } - if ("city" in geoZone && geoZone.city) { - identifier += geoZone.city + if (geoZone.city) { + identifier.push("c:" + geoZone.city) } - if ("postal_expression" in geoZone && geoZone.postal_expression) { - identifier += JSON.stringify(geoZone.postal_expression) + if (geoZone.postal_expression) { + identifier.push("pe:" + JSON.stringify(geoZone.postal_expression)) } - return identifier + return identifier.join("_") } /** @@ -116,7 +111,7 @@ export default class FulfillmentModuleService< * @param sharedContext * @protected */ - protected async prepareCreateData( + /*protected async prepareCreateData( data: ( | FulfillmentTypes.CreateFulfillmentSetDTO | FulfillmentTypes.UpdateFulfillmentSetDTO @@ -207,7 +202,7 @@ export default class FulfillmentModuleService< existingGeoZones, existingGeoZonesMap, } - } + }*/ create( data: FulfillmentTypes.CreateFulfillmentSetDTO[], @@ -245,170 +240,8 @@ export default class FulfillmentModuleService< ): Promise { const data_ = Array.isArray(data) ? data : [data] - const fulfillmentSetMap = new Map< - string, - FulfillmentTypes.CreateFulfillmentSetDTO - >() - - const fulfillmentSetServiceZonesMap = new Map< - string, - Map< - string, - Required["service_zones"][number] - > - >() - - const serviceZoneGeoZonesMap = new Map< - string, - Map - >() - - const serviceZonesToCreate: FulfillmentTypes.CreateServiceZoneDTO[] = [] - const geoZonesToCreate: FulfillmentTypes.CreateGeoZoneDTO[] = [] - - const { - existingServiceZones, - existingServiceZonesMap, - existingGeoZones, - existingGeoZonesMap, - } = await this.prepareCreateData(data_, sharedContext) - - data_.forEach(({ service_zones, ...fulfillmentSetDataOnly }) => { - fulfillmentSetMap.set(fulfillmentSetDataOnly.name, fulfillmentSetDataOnly) - - /** - * If there is any service zone to process - * store the service zones to create while populating the fulfillment set service zone map - * in order to be able after creating the service zones to re update the map with the - * newly created service zones and then assign them to the fulfillment sets to be - * create attached. - */ - - if (service_zones?.length) { - const serviceZoneTuple: [ - string, - Required["service_zones"][number] - ][] = service_zones.map((serviceZone) => { - let geoZoneTuple: [ - string, - FulfillmentTypes.CreateGeoZoneDTO | { id: string } - ][] = [] - - if ("geo_zones" in serviceZone && serviceZone.geo_zones) { - const geo_zones = serviceZone.geo_zones - delete serviceZone.geo_zones - - geoZoneTuple = geo_zones.map((geoZone) => { - const existingGeoZone = - "id" in geoZone ? existingGeoZonesMap.get(geoZone.id)! : geoZone - - if (!("id" in geoZone)) { - geoZonesToCreate.push(geoZone) - } - - const geoZoneIdentifier = - FulfillmentModuleService.getGeoZoneIdentifier(geoZone) - - return [geoZoneIdentifier, existingGeoZone] - }) - } - - const existingZone = - "id" in serviceZone - ? existingServiceZonesMap.get(serviceZone.id)! - : serviceZone - - if (!("id" in serviceZone)) { - serviceZonesToCreate.push(serviceZone) - } - - const serviceZoneIdentifier = - "id" in serviceZone ? serviceZone.id : serviceZone.name - - serviceZoneGeoZonesMap.set( - serviceZoneIdentifier, - new Map(geoZoneTuple) - ) - - return [serviceZoneIdentifier, existingZone] - }) - - fulfillmentSetServiceZonesMap.set( - fulfillmentSetDataOnly.name, - new Map(serviceZoneTuple) - ) - } - }) - - if (geoZonesToCreate.length) { - // deduplicate geo zones to create - const geoZoneToCreateMap = new Map( - geoZonesToCreate.map((geoZone) => [ - FulfillmentModuleService.getGeoZoneIdentifier(geoZone), - geoZone, - ]) - ) - const createdGeoZones = await this.geoZoneService_.create( - [...geoZoneToCreateMap.values()], - sharedContext - ) - - for (const [, geoZoneMap] of serviceZoneGeoZonesMap) { - for (const createdGeoZone of createdGeoZones) { - const geoZoneIdentifier = - FulfillmentModuleService.getGeoZoneIdentifier(createdGeoZone, { - preventIdUsage: true, - }) - - if (geoZoneMap.has(geoZoneIdentifier)) { - geoZoneMap.set(geoZoneIdentifier, createdGeoZone) - } - } - } - } - - // re assign the geo zones to the service zones - for (const serviceZone of serviceZonesToCreate) { - if (serviceZoneGeoZonesMap.has(serviceZone.name)) { - const geoZones = serviceZoneGeoZonesMap.get(serviceZone.name)!.values() - serviceZone.geo_zones = [...geoZones] - } - } - - if (serviceZonesToCreate.length) { - // Deduplicate service zones to create - const serviceZoneToCreateMap = new Map( - serviceZonesToCreate.map((serviceZone) => [ - serviceZone.name, - serviceZone, - ]) - ) - const createdServiceZones = await this.serviceZoneService_.create( - [...serviceZoneToCreateMap.values()], - sharedContext - ) - - for (const [, serviceZoneMap] of fulfillmentSetServiceZonesMap) { - for (const createdServiceZone of createdServiceZones) { - if (serviceZoneMap.has(createdServiceZone.name)) { - serviceZoneMap.set(createdServiceZone.name, createdServiceZone) - } - } - } - } - - // re assign the service zones to the fulfillment sets - for (const fulfillmentSet of fulfillmentSetMap.values()) { - if (fulfillmentSetServiceZonesMap.has(fulfillmentSet.name)) { - const serviceZones = fulfillmentSetServiceZonesMap - .get(fulfillmentSet.name)! - .values() - fulfillmentSet.service_zones = [...serviceZones] - } - } - const createdFulfillmentSets = await this.fulfillmentSetService_.create( - [...fulfillmentSetMap.values()], + data_, sharedContext ) @@ -454,83 +287,17 @@ export default class FulfillmentModuleService< | FulfillmentTypes.CreateServiceZoneDTO, sharedContext?: Context ): Promise { - const data_ = Array.isArray(data) ? data : [data] - - const geoZonesToCreate: FulfillmentTypes.CreateGeoZoneDTO[] = [] - const serviceZoneGeoZonesMap = new Map< - string, - Map - >() - - /** - * The reasoning behind the following code that we want to deduplicate potential - * duplicated in order to be able to reuse the same one even though the - * user provides the same geo zone in multiple service zones. - */ - - data_.forEach((serviceZone) => { - if ("geo_zones" in serviceZone && serviceZone.geo_zones) { - const geo_zones = serviceZone.geo_zones - delete serviceZone.geo_zones + let data_ = Array.isArray(data) ? data : [data] - const geoZoneTuple: [ - string, - FulfillmentTypes.CreateGeoZoneDTO | { id: string } - ][] = geo_zones.map((geoZone) => { - if (!("id" in geoZone)) { - geoZonesToCreate.push(geoZone) - } - - const geoZoneIdentifier = - FulfillmentModuleService.getGeoZoneIdentifier(geoZone) - - return [geoZoneIdentifier, geoZone] - }) - - serviceZoneGeoZonesMap.set(serviceZone.name, new Map(geoZoneTuple)) - } - }) - - if (geoZonesToCreate.length) { - // Deduplicate geo zones to create - const geoZoneToCreateMap = new Map( - geoZonesToCreate.map((geoZone) => [ - FulfillmentModuleService.getGeoZoneIdentifier(geoZone), - geoZone, - ]) - ) - - const createdGeoZones = await this.geoZoneService_.create( - [...geoZoneToCreateMap.values()], - sharedContext - ) - - for (const [serviceZoneName, geoZoneMap] of serviceZoneGeoZonesMap) { - for (const createdGeoZone of createdGeoZones) { - const geoZoneIdentifier = - FulfillmentModuleService.getGeoZoneIdentifier(createdGeoZone, { - preventIdUsage: true, - }) - - if (geoZoneMap.has(geoZoneIdentifier)) { - geoZoneMap.set(geoZoneIdentifier, createdGeoZone) - } - } - } - } - - // re assign the geo zones to the service zones - for (const serviceZone of data_) { - if (serviceZoneGeoZonesMap.has(serviceZone.name)) { - const geoZones = serviceZoneGeoZonesMap.get(serviceZone.name)!.values() - serviceZone.geo_zones = [...geoZones] - } + if (!data_.length) { + return [] } const createdServiceZones = await this.serviceZoneService_.create( data_, sharedContext ) + return Array.isArray(data) ? createdServiceZones : createdServiceZones[0] } @@ -624,7 +391,7 @@ export default class FulfillmentModuleService< data: UpdateFulfillmentSetDTO[] | UpdateFulfillmentSetDTO, sharedContext?: Context ): Promise { - const data_ = Array.isArray(data) ? data : [data] + /*const data_ = Array.isArray(data) ? data : [data] const fulfillmentSetMap = new Map< string, @@ -787,7 +554,8 @@ export default class FulfillmentModuleService< return Array.isArray(data) ? updatedFulfillmentSets - : updatedFulfillmentSets[0] + : updatedFulfillmentSets[0]*/ + return [] } updateServiceZones( @@ -826,7 +594,7 @@ export default class FulfillmentModuleService< | FulfillmentTypes.UpdateServiceZoneDTO, sharedContext?: Context ): Promise { - const data_ = Array.isArray(data) ? data : [data] + /*const data_ = Array.isArray(data) ? data : [data] const geoZonesToCreate: FulfillmentTypes.CreateGeoZoneDTO[] = [] const serviceZoneGeoZonesMap = new Map< @@ -931,7 +699,8 @@ export default class FulfillmentModuleService< sharedContext ) - return Array.isArray(data) ? updatedServiceZones : updatedServiceZones[0] + return Array.isArray(data) ? updatedServiceZones : updatedServiceZones[0]*/ + return [] } updateShippingOptions( diff --git a/packages/types/src/fulfillment/mutations/fulfillment-set.ts b/packages/types/src/fulfillment/mutations/fulfillment-set.ts index 970a032514260..52f02b9f16508 100644 --- a/packages/types/src/fulfillment/mutations/fulfillment-set.ts +++ b/packages/types/src/fulfillment/mutations/fulfillment-set.ts @@ -3,7 +3,9 @@ import { CreateServiceZoneDTO } from "./service-zone" export interface CreateFulfillmentSetDTO { name: string type: string - service_zones?: (CreateServiceZoneDTO | { id: string })[] + service_zones?: (Omit & { + fulfillment_set?: string + })[] } export interface UpdateFulfillmentSetDTO diff --git a/packages/types/src/fulfillment/mutations/geo-zone.ts b/packages/types/src/fulfillment/mutations/geo-zone.ts index e591ed6cfd8db..087e9f6eada19 100644 --- a/packages/types/src/fulfillment/mutations/geo-zone.ts +++ b/packages/types/src/fulfillment/mutations/geo-zone.ts @@ -2,30 +2,27 @@ import { GeoZoneType } from "../common" interface CreateGeoZoneBaseDTO { type: GeoZoneType + service_zone_id: string country_code: string metadata?: Record | null } interface CreateCountryGeoZoneDTO extends CreateGeoZoneBaseDTO { type: "country" - country_code: string } interface CreateProvinceGeoZoneDTO extends CreateGeoZoneBaseDTO { type: "province" - country_code: string province_code: string } interface CreateCityGeoZoneDTO extends CreateGeoZoneBaseDTO { type: "city" - country_code: string city: string } interface CreateZipGeoZoneDTO extends CreateGeoZoneBaseDTO { type: "zip" - country_code: string postal_expression: Record } @@ -41,24 +38,20 @@ interface UpdateGeoZoneBaseDTO extends Partial { interface UpdateCountryGeoZoneDTO extends UpdateGeoZoneBaseDTO { type: "country" - country_code: string } interface UpdateProvinceGeoZoneDTO extends UpdateGeoZoneBaseDTO { type: "province" - country_code: string province_code: string } interface UpdateCityGeoZoneDTO extends UpdateGeoZoneBaseDTO { type: "city" - country_code: string city: string } interface UpdateZipGeoZoneDTO extends UpdateGeoZoneBaseDTO { type: "zip" - country_code: string postal_expression: Record } diff --git a/packages/types/src/fulfillment/mutations/service-zone.ts b/packages/types/src/fulfillment/mutations/service-zone.ts index 1152bb35ed199..7d9e7461aa6f1 100644 --- a/packages/types/src/fulfillment/mutations/service-zone.ts +++ b/packages/types/src/fulfillment/mutations/service-zone.ts @@ -2,7 +2,10 @@ import { CreateGeoZoneDTO } from "./geo-zone" export interface CreateServiceZoneDTO { name: string - geo_zones?: (CreateGeoZoneDTO | { id: string })[] + fulfillment_set_id: string + geo_zones?: (Omit & { + service_zone_id?: string + })[] } export interface UpdateServiceZoneDTO extends Partial { From 479185cacc71faecbcd2dff8a6e041e59a2603e0 Mon Sep 17 00:00:00 2001 From: adrien2p Date: Wed, 14 Feb 2024 13:35:07 +0100 Subject: [PATCH 27/29] finalise rework --- .../fulfillment-module-service.spec.ts | 198 ++++---- .../fulfillment/src/models/fullfilment-set.ts | 3 +- packages/fulfillment/src/models/geo-zone.ts | 6 +- .../fulfillment/src/models/service-zone.ts | 5 +- .../services/fulfillment-module-service.ts | 468 +++++++++--------- .../fulfillment/mutations/fulfillment-set.ts | 10 +- .../src/fulfillment/mutations/service-zone.ts | 8 +- 7 files changed, 327 insertions(+), 371 deletions(-) diff --git a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts index debfca75cb782..eb85670c2e6e4 100644 --- a/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts +++ b/packages/fulfillment/integration-tests/__tests__/fulfillment-module-service.spec.ts @@ -4,7 +4,6 @@ import { CreateFulfillmentSetDTO, CreateGeoZoneDTO, CreateServiceZoneDTO, - FulfillmentSetDTO, GeoZoneDTO, IFulfillmentModuleService, ServiceZoneDTO, @@ -117,11 +116,18 @@ describe("fulfillment module service", function () { describe("service zones", () => { it("should list service zones with a filter", async function () { + const fulfillmentSet = await service.create({ + name: "test", + type: "test-type", + }) + const createdZone1 = await service.createServiceZones({ name: "test", + fulfillment_set_id: fulfillmentSet.id, }) const createdZone2 = await service.createServiceZones({ name: "test2", + fulfillment_set_id: fulfillmentSet.id, geo_zones: [ { type: GeoZoneType.COUNTRY, @@ -164,11 +170,22 @@ describe("fulfillment module service", function () { describe("geo zones", () => { it("should list geo zones with a filter", async function () { + const fulfillmentSet = await service.create({ + name: "test", + type: "test-type", + }) + const serviceZone = await service.createServiceZones({ + name: "test", + fulfillment_set_id: fulfillmentSet.id, + }) + const createdZone1 = await service.createGeoZones({ + service_zone_id: serviceZone.id, type: GeoZoneType.COUNTRY, country_code: "fr", }) const createdZone2 = await service.createGeoZones({ + service_zone_id: serviceZone.id, type: GeoZoneType.COUNTRY, country_code: "us", }) @@ -294,7 +311,7 @@ describe("fulfillment module service", function () { type: "test-type2", service_zones: [ { - name: "test", + name: "test2", }, ], }, @@ -303,7 +320,7 @@ describe("fulfillment module service", function () { type: "test-type3", service_zones: [ { - name: "test2", + name: "test3", }, ], }, @@ -742,6 +759,7 @@ describe("fulfillment module service", function () { const createdFulfillmentSet = await service.create(createData) const createServiceZoneData: CreateServiceZoneDTO = { + fulfillment_set_id: createdFulfillmentSet.id, name: "service-zone-test2", geo_zones: [ { @@ -784,16 +802,12 @@ describe("fulfillment module service", function () { }) ) - // Validate pivot table no oprhan data left - const pivotTableData = await MikroOrmWrapper.forkManager().execute( - "SELECT * from fulfillment_set_service_zones" - ) + const serviceZones = await service.listServiceZones() - expect(pivotTableData).toHaveLength(1) - expect(pivotTableData[0]).toEqual( + expect(serviceZones).toHaveLength(1) + expect(serviceZones[0]).toEqual( expect.objectContaining({ - fulfillment_set_id: createdFulfillmentSet.id, - service_zone_id: updatedFulfillmentSet.service_zones[0].id, + id: updatedFulfillmentSet.service_zones[0].id, }) ) }) @@ -818,6 +832,7 @@ describe("fulfillment module service", function () { const createdFulfillmentSet = await service.create(createData) const createServiceZoneData: CreateServiceZoneDTO = { + fulfillment_set_id: createdFulfillmentSet.id, name: "service-zone-test2", geo_zones: [ { @@ -936,7 +951,7 @@ describe("fulfillment module service", function () { type: `updated-test-type${index + 1}`, service_zones: [ { - name: `new-service-zone-test`, + name: `new-service-zone-test${index + 1}`, geo_zones: [ { type: GeoZoneType.COUNTRY, @@ -978,21 +993,16 @@ describe("fulfillment module service", function () { ++i } - // Validate pivot table no oprhan data left - const pivotTableData = await MikroOrmWrapper.forkManager().execute( - "SELECT * from fulfillment_set_service_zones" - ) + const serviceZones = await service.listServiceZones() - expect(pivotTableData).toHaveLength(2) - expect(pivotTableData).toEqual( + expect(serviceZones).toHaveLength(2) + expect(serviceZones).toEqual( expect.arrayContaining([ expect.objectContaining({ - fulfillment_set_id: updatedFulfillmentSets[0].id, - service_zone_id: updatedFulfillmentSets[0].service_zones[0].id, + name: updateData[0].service_zones![0].name, }), expect.objectContaining({ - fulfillment_set_id: updatedFulfillmentSets[1].id, - service_zone_id: updatedFulfillmentSets[1].service_zones[0].id, + name: updateData[1].service_zones![0].name, }), ]) ) @@ -1042,7 +1052,7 @@ describe("fulfillment module service", function () { service_zones: [ ...fulfillmentSet.service_zones, { - name: `added-service-zone-test`, + name: `added-service-zone-test${index + 1}`, geo_zones: [ { type: GeoZoneType.COUNTRY, @@ -1087,29 +1097,22 @@ describe("fulfillment module service", function () { ++i } - // Validate pivot table no oprhan data left - const pivotTableData = await MikroOrmWrapper.forkManager().execute( - "SELECT * from fulfillment_set_service_zones" - ) + const serviceZones = await service.listServiceZones() - expect(pivotTableData).toHaveLength(4) - expect(pivotTableData).toEqual( + expect(serviceZones).toHaveLength(4) + expect(serviceZones).toEqual( expect.arrayContaining([ expect.objectContaining({ - fulfillment_set_id: updatedFulfillmentSets[0].id, - service_zone_id: updatedFulfillmentSets[0].service_zones[0].id, + name: createdFulfillmentSets[0].service_zones![0].name, }), expect.objectContaining({ - fulfillment_set_id: updatedFulfillmentSets[1].id, - service_zone_id: updatedFulfillmentSets[1].service_zones[0].id, + name: createdFulfillmentSets[1].service_zones![0].name, }), expect.objectContaining({ - fulfillment_set_id: updatedFulfillmentSets[0].id, - service_zone_id: updatedFulfillmentSets[0].service_zones[1].id, + name: updateData[0].service_zones![1].name, }), expect.objectContaining({ - fulfillment_set_id: updatedFulfillmentSets[1].id, - service_zone_id: updatedFulfillmentSets[1].service_zones[1].id, + name: updateData[1].service_zones![1].name, }), ]) ) @@ -1118,8 +1121,14 @@ describe("fulfillment module service", function () { describe("on update service zones", () => { it("should update an existing service zone", async function () { + const fulfillmentSet = await service.create({ + name: "test", + type: "test-type", + }) + const createData: CreateServiceZoneDTO = { name: "service-zone-test", + fulfillment_set_id: fulfillmentSet.id, geo_zones: [ { type: GeoZoneType.COUNTRY, @@ -1160,9 +1169,15 @@ describe("fulfillment module service", function () { }) it("should update a collection of service zones", async function () { + const fulfillmentSet = await service.create({ + name: "test", + type: "test-type", + }) + const createData: CreateServiceZoneDTO[] = [ { name: "service-zone-test", + fulfillment_set_id: fulfillmentSet.id, geo_zones: [ { type: GeoZoneType.COUNTRY, @@ -1172,6 +1187,7 @@ describe("fulfillment module service", function () { }, { name: "service-zone-test2", + fulfillment_set_id: fulfillmentSet.id, geo_zones: [ { type: GeoZoneType.COUNTRY, @@ -1188,7 +1204,6 @@ describe("fulfillment module service", function () { name: `updated-service-zone-test${index + 1}`, geo_zones: [ { - id: serviceZone.geo_zones[0].id, type: GeoZoneType.COUNTRY, country_code: index % 2 === 0 ? "us" : "fr", }, @@ -1207,7 +1222,6 @@ describe("fulfillment module service", function () { name: data_.name, geo_zones: expect.arrayContaining([ expect.objectContaining({ - id: data_.geo_zones[0].id, type: data_.geo_zones[0].type, country_code: data_.geo_zones[0].country_code, }), @@ -1219,9 +1233,15 @@ describe("fulfillment module service", function () { }) it("should fail on duplicated service zone name", async function () { + const fulfillmentSet = await service.create({ + name: "test", + type: "test-type", + }) + const createData: CreateServiceZoneDTO[] = [ { name: "service-zone-test", + fulfillment_set_id: fulfillmentSet.id, geo_zones: [ { type: GeoZoneType.COUNTRY, @@ -1231,6 +1251,7 @@ describe("fulfillment module service", function () { }, { name: "service-zone-test2", + fulfillment_set_id: fulfillmentSet.id, geo_zones: [ { type: GeoZoneType.COUNTRY, @@ -1244,10 +1265,9 @@ describe("fulfillment module service", function () { const updateData = { id: createdServiceZones[1].id, - name: "service-zone-test", // This is the name of the first service zone + name: "service-zone-test", geo_zones: [ { - id: createdServiceZones[1].geo_zones[0].id, type: GeoZoneType.COUNTRY, country_code: "us", }, @@ -1263,7 +1283,18 @@ describe("fulfillment module service", function () { describe("on update geo zones", () => { it("should update an existing geo zone", async function () { + const fulfillmentSet = await service.create({ + name: "test", + type: "test-type", + }) + + const serviceZone = await service.createServiceZones({ + name: "test", + fulfillment_set_id: fulfillmentSet.id, + }) + const createData: CreateGeoZoneDTO = { + service_zone_id: serviceZone.id, type: GeoZoneType.COUNTRY, country_code: "fr", } @@ -1288,12 +1319,24 @@ describe("fulfillment module service", function () { }) it("should update a collection of geo zones", async function () { + const fulfillmentSet = await service.create({ + name: "test", + type: "test-type", + }) + + const serviceZone = await service.createServiceZones({ + name: "test", + fulfillment_set_id: fulfillmentSet.id, + }) + const createData: CreateGeoZoneDTO[] = [ { + service_zone_id: serviceZone.id, type: GeoZoneType.COUNTRY, country_code: "fr", }, { + service_zone_id: serviceZone.id, type: GeoZoneType.COUNTRY, country_code: "us", }, @@ -1326,78 +1369,5 @@ describe("fulfillment module service", function () { } }) }) - - describe("on delete", () => { - it("should delete a fulfillment set", async function () { - const createdSet = await service.create({ - name: "test", - type: "test-type", - service_zones: [ - { - name: "test", - geo_zones: [ - { - type: GeoZoneType.COUNTRY, - country_code: "fr", - }, - ], - }, - ], - }) - - await service.delete(createdSet.id) - - let retrievedSet: FulfillmentSetDTO | undefined - await service - .retrieve(createdSet.id) - .then((set) => (retrievedSet = set)) - .catch((e) => e) - - expect(retrievedSet).toBeUndefined() - }) - }) - - describe("on delete service zones", () => { - it("should delete a service zone", async function () { - const createdZone = await service.createServiceZones({ - name: "test", - geo_zones: [ - { - type: GeoZoneType.COUNTRY, - country_code: "fr", - }, - ], - }) - - await service.deleteServiceZones(createdZone.id) - - let retrievedZone: ServiceZoneDTO | undefined - await service - .retrieveServiceZone(createdZone.id) - .then((zone) => (retrievedZone = zone)) - .catch((e) => e) - - expect(retrievedZone).toBeUndefined() - }) - }) - - describe("on delete geo zones", () => { - it("should delete a geo zone", async function () { - const createdZone = await service.createGeoZones({ - type: GeoZoneType.COUNTRY, - country_code: "fr", - }) - - await service.deleteGeoZones(createdZone.id) - - let retrievedZone: GeoZoneDTO | undefined - await service - .retrieveGeoZone(createdZone.id) - .then((zone) => (retrievedZone = zone)) - .catch((e) => e) - - expect(retrievedZone).toBeUndefined() - }) - }) }) }) diff --git a/packages/fulfillment/src/models/fullfilment-set.ts b/packages/fulfillment/src/models/fullfilment-set.ts index d58994bcf2579..38c056ea11fde 100644 --- a/packages/fulfillment/src/models/fullfilment-set.ts +++ b/packages/fulfillment/src/models/fullfilment-set.ts @@ -61,7 +61,8 @@ export default class FulfillmentSet { metadata: Record | null = null @OneToMany(() => ServiceZone, "fulfillment_set", { - cascade: [Cascade.REMOVE, "soft-remove"] as any, + cascade: [Cascade.PERSIST, "soft-remove"] as any, + orphanRemoval: true, }) service_zones = new Collection(this) diff --git a/packages/fulfillment/src/models/geo-zone.ts b/packages/fulfillment/src/models/geo-zone.ts index a8cdc7076314c..3104a4c4fb2fe 100644 --- a/packages/fulfillment/src/models/geo-zone.ts +++ b/packages/fulfillment/src/models/geo-zone.ts @@ -108,7 +108,9 @@ export default class GeoZone { @Property({ columnType: "jsonb", nullable: true }) metadata: Record | null = null - @ManyToOne(() => ServiceZone, { persist: false }) + @ManyToOne(() => ServiceZone, { + persist: false, + }) service_zone: ServiceZone @Property({ @@ -136,10 +138,12 @@ export default class GeoZone { @BeforeCreate() onCreate() { this.id = generateEntityId(this.id, " fgz") + this.service_zone_id ??= this.service_zone?.id } @OnInit() onInit() { this.id = generateEntityId(this.id, "fgz") + this.service_zone_id ??= this.service_zone?.id } } diff --git a/packages/fulfillment/src/models/service-zone.ts b/packages/fulfillment/src/models/service-zone.ts index 8e6782be318a2..5ca0fe6a25da2 100644 --- a/packages/fulfillment/src/models/service-zone.ts +++ b/packages/fulfillment/src/models/service-zone.ts @@ -79,7 +79,8 @@ export default class ServiceZone { fulfillment_set: FulfillmentSet @OneToMany(() => GeoZone, "service_zone", { - cascade: [Cascade.REMOVE, "soft-remove"] as any, + cascade: [Cascade.PERSIST, "soft-remove"] as any, + orphanRemoval: true, }) geo_zones = new Collection(this) @@ -114,10 +115,12 @@ export default class ServiceZone { @BeforeCreate() onCreate() { this.id = generateEntityId(this.id, "serzo") + this.fulfillment_set_id ??= this.fulfillment_set?.id } @OnInit() onInit() { this.id = generateEntityId(this.id, "serzo") + this.fulfillment_set_id ??= this.fulfillment_set?.id } } diff --git a/packages/fulfillment/src/services/fulfillment-module-service.ts b/packages/fulfillment/src/services/fulfillment-module-service.ts index 2d7aed35ef56a..d3b8efa630ad1 100644 --- a/packages/fulfillment/src/services/fulfillment-module-service.ts +++ b/packages/fulfillment/src/services/fulfillment-module-service.ts @@ -11,11 +11,15 @@ import { import { InjectManager, InjectTransactionManager, + MedusaContext, + MedusaError, ModulesSdkUtils, + promiseAll, } from "@medusajs/utils" import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config" import { FulfillmentSet, GeoZone, ServiceZone, ShippingOption } from "@models" +import { getSetDifference } from "@medusajs/medusa/dist/utils/diff-set" const generateMethodForModels = [ServiceZone, ShippingOption, GeoZone] @@ -218,7 +222,7 @@ export default class FulfillmentModuleService< data: | FulfillmentTypes.CreateFulfillmentSetDTO | FulfillmentTypes.CreateFulfillmentSetDTO[], - sharedContext?: Context + @MedusaContext() sharedContext: Context = {} ): Promise< FulfillmentTypes.FulfillmentSetDTO | FulfillmentTypes.FulfillmentSetDTO[] > { @@ -236,7 +240,7 @@ export default class FulfillmentModuleService< data: | FulfillmentTypes.CreateFulfillmentSetDTO | FulfillmentTypes.CreateFulfillmentSetDTO[], - sharedContext?: Context + @MedusaContext() sharedContext: Context = {} ): Promise { const data_ = Array.isArray(data) ? data : [data] @@ -264,7 +268,7 @@ export default class FulfillmentModuleService< data: | FulfillmentTypes.CreateServiceZoneDTO[] | FulfillmentTypes.CreateServiceZoneDTO, - sharedContext?: Context + @MedusaContext() sharedContext: Context = {} ): Promise< FulfillmentTypes.ServiceZoneDTO | FulfillmentTypes.ServiceZoneDTO[] > { @@ -285,7 +289,7 @@ export default class FulfillmentModuleService< data: | FulfillmentTypes.CreateServiceZoneDTO[] | FulfillmentTypes.CreateServiceZoneDTO, - sharedContext?: Context + @MedusaContext() sharedContext: Context = {} ): Promise { let data_ = Array.isArray(data) ? data : [data] @@ -315,7 +319,7 @@ export default class FulfillmentModuleService< data: | FulfillmentTypes.CreateShippingOptionDTO[] | FulfillmentTypes.CreateShippingOptionDTO, - sharedContext?: Context + @MedusaContext() sharedContext: Context = {} ): Promise< FulfillmentTypes.ShippingOptionDTO | FulfillmentTypes.ShippingOptionDTO[] > { @@ -336,7 +340,7 @@ export default class FulfillmentModuleService< data: | FulfillmentTypes.CreateGeoZoneDTO | FulfillmentTypes.CreateGeoZoneDTO[], - sharedContext?: Context + @MedusaContext() sharedContext: Context = {} ): Promise { const createdGeoZones = await this.geoZoneService_.create( data, @@ -363,7 +367,7 @@ export default class FulfillmentModuleService< @InjectManager("baseRepository_") async update( data: UpdateFulfillmentSetDTO[] | UpdateFulfillmentSetDTO, - sharedContext?: Context + @MedusaContext() sharedContext: Context = {} ): Promise< FulfillmentTypes.FulfillmentSetDTO[] | FulfillmentTypes.FulfillmentSetDTO > { @@ -376,186 +380,154 @@ export default class FulfillmentModuleService< }) } - /** - * Update fulfillment sets. This method is responsible for updating the fulfillment - * sets and the service zones that are attached to the fulfillment. The geo zones are - * discarded here because they are not directly attached to the fulfillment set. - * Instead, the user can create and update the geo zones through the service zone - * or create a new service zone to be attached to the fulfillment set. - * - * @param data - * @param sharedContext - */ @InjectTransactionManager("baseRepository_") protected async update_( data: UpdateFulfillmentSetDTO[] | UpdateFulfillmentSetDTO, - sharedContext?: Context + @MedusaContext() sharedContext: Context = {} ): Promise { - /*const data_ = Array.isArray(data) ? data : [data] - - const fulfillmentSetMap = new Map< - string, - FulfillmentTypes.UpdateFulfillmentSetDTO - >() - - const fulfillmentSetServiceZonesMap = new Map< - string, - Map< - string, - Required["service_zones"][number] - > - >() - - const serviceZoneGeoZonesMap = new Map< - string, - Map - >() - - const serviceZonesToCreate: FulfillmentTypes.CreateServiceZoneDTO[] = [] - const geoZonesToCreate: FulfillmentTypes.CreateGeoZoneDTO[] = [] - - const { - existingServiceZones, - existingServiceZonesMap, - existingGeoZones, - existingGeoZonesMap, - } = await this.prepareCreateData(data_, sharedContext) - - data_.forEach(({ service_zones, ...fulfillmentSetDataOnly }) => { - fulfillmentSetMap.set(fulfillmentSetDataOnly.id, fulfillmentSetDataOnly) - - if (service_zones?.length) { - const serviceZoneTuple: [ - string, - Required["service_zones"][number] - ][] = service_zones.map((serviceZone) => { - let geoZoneTuple: [ - string, - FulfillmentTypes.CreateGeoZoneDTO | { id: string } - ][] = [] - - if ("geo_zones" in serviceZone && serviceZone.geo_zones) { - const geo_zones = serviceZone.geo_zones - delete serviceZone.geo_zones - - geoZoneTuple = geo_zones.map((geoZone) => { - const existingGeoZone = - "id" in geoZone ? existingGeoZonesMap.get(geoZone.id)! : geoZone - - if (!("id" in geoZone)) { - geoZonesToCreate.push(geoZone) - } - - const geoZoneIdentifier = - FulfillmentModuleService.getGeoZoneIdentifier(geoZone) - - return [geoZoneIdentifier, existingGeoZone] - }) - } - - const existingZone = - "id" in serviceZone - ? existingServiceZonesMap.get(serviceZone.id)! - : null - - if (!("id" in serviceZone)) { - serviceZonesToCreate.push(serviceZone) - } - - const serviceZoneIdentifier = - "id" in serviceZone ? serviceZone.id : serviceZone.name + const data_ = Array.isArray(data) ? data : [data] - serviceZoneGeoZonesMap.set( - serviceZoneIdentifier, - new Map(geoZoneTuple) - ) + if (!data_.length) { + return [] + } - return [serviceZoneIdentifier, existingZone ?? serviceZone] - }) + const fulfillmentSetIds = data_.map((f) => f.id) + if (!fulfillmentSetIds.length) { + return [] + } - fulfillmentSetServiceZonesMap.set( - fulfillmentSetDataOnly.id, - new Map(serviceZoneTuple) - ) - } - }) + const fulfillmentSets = await this.fulfillmentSetService_.list( + { + id: fulfillmentSetIds, + }, + { + relations: ["service_zones", "service_zones.geo_zones"], + }, + sharedContext + ) - if (geoZonesToCreate.length) { - const geoZoneToUpdateMap = new Map( - geoZonesToCreate.map((geoZone) => [ - FulfillmentModuleService.getGeoZoneIdentifier(geoZone), - geoZone, - ]) - ) + const fulfillmentSetSet = new Set(fulfillmentSets.map((f) => f.id)) + const expectedFulfillmentSetSet = new Set(data_.map((f) => f.id)) + const missingFulfillmentSetIds = getSetDifference( + expectedFulfillmentSetSet, + fulfillmentSetSet + ) - const createdGeoZones = await this.geoZoneService_.create( - [...geoZoneToUpdateMap.values()], - sharedContext + if (missingFulfillmentSetIds.size) { + throw new MedusaError( + MedusaError.Types.NOT_FOUND, + `The following fulfillment sets does not exists: ${Array.from( + missingFulfillmentSetIds + ).join(", ")}` ) + } - for (const [, geoZoneMap] of serviceZoneGeoZonesMap) { - for (const createdGeoZone of createdGeoZones) { - const geoZoneIdentifier = - FulfillmentModuleService.getGeoZoneIdentifier(createdGeoZone, { - preventIdUsage: true, - }) + const fulfillmentSetMap = new Map( + fulfillmentSets.map((f) => [f.id, f]) + ) - if (geoZoneMap.has(geoZoneIdentifier)) { - geoZoneMap.set(geoZoneIdentifier, createdGeoZone) - } + // find service zones to delete + const serviceZoneIdsToDelete: string[] = [] + const geoZoneIdsToDelete: string[] = [] + data_.forEach((fulfillmentSet) => { + if (fulfillmentSet.service_zones) { + /** + * Detect and delete service zones that are not in the updated + */ + + const existingFulfillmentSet = fulfillmentSetMap.get(fulfillmentSet.id)! + const existingServiceZones = existingFulfillmentSet.service_zones + const updatedServiceZones = fulfillmentSet.service_zones + const toDeleteServiceZoneIds = getSetDifference( + new Set(existingServiceZones.map((s) => s.id)), + new Set( + updatedServiceZones + .map((s) => "id" in s && s.id) + .filter((id): id is string => !!id) + ) + ) + if (toDeleteServiceZoneIds.size) { + serviceZoneIdsToDelete.push(...Array.from(toDeleteServiceZoneIds)) + geoZoneIdsToDelete.push( + ...existingServiceZones + .filter((s) => toDeleteServiceZoneIds.has(s.id)) + .flatMap((s) => s.geo_zones.map((g) => g.id)) + ) } - } - } - // re assign the geo zones to the service zones - for (const serviceZone of serviceZonesToCreate) { - if (serviceZoneGeoZonesMap.has(serviceZone.name)) { - const geoZones = serviceZoneGeoZonesMap.get(serviceZone.name)!.values() - serviceZone.geo_zones = [...geoZones] - } - } + /** + * Detect and re assign service zones to the fulfillment set that are still present + */ - if (serviceZonesToCreate.length) { - const serviceZoneToUpdateMap = new Map( - serviceZonesToCreate.map((serviceZone) => [ - serviceZone.name, - serviceZone, - ]) - ) + const serviceZonesMap = new Map( + existingFulfillmentSet.service_zones.map((serviceZone) => [ + serviceZone.id, + serviceZone, + ]) + ) + const serviceZonesSet = new Set( + existingServiceZones + .map((s) => "id" in s && s.id) + .filter((id): id is string => !!id) + ) + const expectedServiceZoneSet = new Set( + fulfillmentSet.service_zones + .map((s) => "id" in s && s.id) + .filter((id): id is string => !!id) + ) + const missingServiceZoneIds = getSetDifference( + expectedServiceZoneSet, + serviceZonesSet + ) - const createdServiceZones = await this.serviceZoneService_.create( - [...serviceZoneToUpdateMap.values()], - sharedContext - ) + if (missingServiceZoneIds.size) { + throw new MedusaError( + MedusaError.Types.NOT_FOUND, + `The following service zones does not exists: ${Array.from( + missingServiceZoneIds + ).join(", ")}` + ) + } - for (const [, serviceZoneMap] of fulfillmentSetServiceZonesMap) { - for (const updatedServiceZone of createdServiceZones) { - if (serviceZoneMap.has(updatedServiceZone.name)) { - serviceZoneMap.set(updatedServiceZone.name, updatedServiceZone) - } + // re assign service zones to the fulfillment set + if (fulfillmentSet.service_zones) { + fulfillmentSet.service_zones = fulfillmentSet.service_zones.map( + (serviceZone) => { + if (!("id" in serviceZone)) { + return serviceZone + } + return serviceZonesMap.get(serviceZone.id)! + } + ) } } - } + }) - // re assign the service zones to the fulfillment sets - for (const fulfillmentSet of fulfillmentSetMap.values()) { - if (fulfillmentSetServiceZonesMap.has(fulfillmentSet.id)) { - const serviceZones = fulfillmentSetServiceZonesMap - .get(fulfillmentSet.id)! - .values() - fulfillmentSet.service_zones = [...serviceZones] - } + if (serviceZoneIdsToDelete.length) { + await promiseAll([ + this.geoZoneService_.delete( + { + id: geoZoneIdsToDelete, + }, + sharedContext + ), + this.serviceZoneService_.delete( + { + id: serviceZoneIdsToDelete, + }, + sharedContext + ), + ]) } const updatedFulfillmentSets = await this.fulfillmentSetService_.update( - [...fulfillmentSetMap.values()], + data_, sharedContext ) return Array.isArray(data) ? updatedFulfillmentSets - : updatedFulfillmentSets[0]*/ - return [] + : updatedFulfillmentSets[0] } updateServiceZones( @@ -572,7 +544,7 @@ export default class FulfillmentModuleService< data: | FulfillmentTypes.UpdateServiceZoneDTO[] | FulfillmentTypes.UpdateServiceZoneDTO, - sharedContext?: Context + @MedusaContext() sharedContext: Context = {} ): Promise< FulfillmentTypes.ServiceZoneDTO[] | FulfillmentTypes.ServiceZoneDTO > { @@ -588,110 +560,117 @@ export default class FulfillmentModuleService< }) } + @InjectTransactionManager("baseRepository_") protected async updateServiceZones_( data: | FulfillmentTypes.UpdateServiceZoneDTO[] | FulfillmentTypes.UpdateServiceZoneDTO, - sharedContext?: Context + @MedusaContext() sharedContext: Context = {} ): Promise { - /*const data_ = Array.isArray(data) ? data : [data] - - const geoZonesToCreate: FulfillmentTypes.CreateGeoZoneDTO[] = [] - const serviceZoneGeoZonesMap = new Map< - string, - Map - >() + const data_ = Array.isArray(data) ? data : [data] - const existingGeoZones: TGeoZoneEntity[] = [] - let existingGeoZonesMap = new Map() + if (!data_.length) { + return [] + } - const geoZoneIds: string[] = [] + const serviceZoneIds = data_.map((s) => s.id) + if (!serviceZoneIds.length) { + return [] + } - data_.forEach((serviceZone) => { - if ("geo_zones" in serviceZone && serviceZone.geo_zones) { - const geo_zones = serviceZone.geo_zones + const serviceZones = await this.serviceZoneService_.list( + { + id: serviceZoneIds, + }, + { + relations: ["geo_zones"], + }, + sharedContext + ) - geo_zones.forEach((geoZone) => { - if ("id" in geoZone) { - geoZoneIds.push(geoZone.id) - } - }) - } - }) + const serviceZoneSet = new Set(serviceZones.map((s) => s.id)) + const expectedServiceZoneSet = new Set(data_.map((s) => s.id)) + const missingServiceZoneIds = getSetDifference( + expectedServiceZoneSet, + serviceZoneSet + ) - if (geoZoneIds.length) { - existingGeoZones.push( - ...(await this.geoZoneService_.list( - { - id: geoZoneIds, - }, - {}, - sharedContext - )) - ) - existingGeoZonesMap = new Map( - existingGeoZones.map((geoZone) => [geoZone.id, geoZone]) + if (missingServiceZoneIds.size) { + throw new MedusaError( + MedusaError.Types.NOT_FOUND, + `The following service zones does not exists: ${Array.from( + missingServiceZoneIds + ).join(", ")}` ) } - data_.forEach((serviceZone) => { - if ("geo_zones" in serviceZone && serviceZone.geo_zones) { - const geo_zones = serviceZone.geo_zones + const serviceZoneMap = new Map( + serviceZones.map((s) => [s.id, s]) + ) - const geoZoneTuple: [ - string, - FulfillmentTypes.CreateGeoZoneDTO | { id: string } - ][] = geo_zones.map((geoZone) => { - if (!("id" in geoZone)) { - geoZonesToCreate.push(geoZone) - } + const serviceZoneIdsToDelete: string[] = [] + const geoZoneIdsToDelete: string[] = [] - const existingZone = - "id" in geoZone ? existingGeoZonesMap.get(geoZone.id)! : geoZone + data_.forEach((serviceZone) => { + if (serviceZone.geo_zones) { + const existingServiceZone = serviceZoneMap.get(serviceZone.id)! + const existingGeoZones = existingServiceZone.geo_zones + const updatedGeoZones = serviceZone.geo_zones + const toDeleteGeoZoneIds = getSetDifference( + new Set(existingGeoZones.map((g) => g.id)), + new Set( + updatedGeoZones + .map((g) => "id" in g && g.id) + .filter((id): id is string => !!id) + ) + ) + if (toDeleteGeoZoneIds.size) { + geoZoneIdsToDelete.push(...Array.from(toDeleteGeoZoneIds)) + } - const geoZoneIdentifier = - FulfillmentModuleService.getGeoZoneIdentifier(geoZone) + const geoZonesMap = new Map( + existingServiceZone.geo_zones.map((geoZone) => [geoZone.id, geoZone]) + ) + const geoZonesSet = new Set( + existingGeoZones + .map((g) => "id" in g && g.id) + .filter((id): id is string => !!id) + ) + const expectedGeoZoneSet = new Set( + serviceZone.geo_zones + .map((g) => "id" in g && g.id) + .filter((id): id is string => !!id) + ) + const missingGeoZoneIds = getSetDifference( + expectedGeoZoneSet, + geoZonesSet + ) - return [geoZoneIdentifier, existingZone] - }) + if (missingGeoZoneIds.size) { + throw new MedusaError( + MedusaError.Types.NOT_FOUND, + `The following geo zones does not exists: ${Array.from( + missingGeoZoneIds + ).join(", ")}` + ) + } - serviceZoneGeoZonesMap.set(serviceZone.id, new Map(geoZoneTuple)) + serviceZone.geo_zones = serviceZone.geo_zones.map((geoZone) => { + if (!("id" in geoZone)) { + return geoZone + } + return geoZonesMap.get(geoZone.id)! + }) } }) - if (geoZonesToCreate.length) { - const geoZoneToUpdateMap = new Map( - geoZonesToCreate.map((geoZone) => [ - FulfillmentModuleService.getGeoZoneIdentifier(geoZone), - geoZone, - ]) - ) - - const createdGeoZones = await this.geoZoneService_.create( - [...geoZoneToUpdateMap.values()], + if (geoZoneIdsToDelete.length) { + await this.geoZoneService_.delete( + { + id: geoZoneIdsToDelete, + }, sharedContext ) - - for (const [, geoZoneMap] of serviceZoneGeoZonesMap) { - for (const createdGeoZone of createdGeoZones) { - const geoZoneIdentifier = - FulfillmentModuleService.getGeoZoneIdentifier(createdGeoZone, { - preventIdUsage: true, - }) - - if (geoZoneMap.has(geoZoneIdentifier)) { - geoZoneMap.set(geoZoneIdentifier, createdGeoZone) - } - } - } - } - - // re assign the geo zones to the service zones - for (const serviceZone of data_) { - if (serviceZoneGeoZonesMap.has(serviceZone.id)) { - const geoZones = serviceZoneGeoZonesMap.get(serviceZone.id)!.values() - serviceZone.geo_zones = [...geoZones] - } } const updatedServiceZones = await this.serviceZoneService_.update( @@ -699,8 +678,7 @@ export default class FulfillmentModuleService< sharedContext ) - return Array.isArray(data) ? updatedServiceZones : updatedServiceZones[0]*/ - return [] + return Array.isArray(data) ? updatedServiceZones : updatedServiceZones[0] } updateShippingOptions( @@ -717,7 +695,7 @@ export default class FulfillmentModuleService< data: | FulfillmentTypes.UpdateShippingOptionDTO[] | FulfillmentTypes.UpdateShippingOptionDTO, - sharedContext?: Context + @MedusaContext() sharedContext: Context = {} ): Promise< FulfillmentTypes.ShippingOptionDTO[] | FulfillmentTypes.ShippingOptionDTO > { @@ -738,7 +716,7 @@ export default class FulfillmentModuleService< data: | FulfillmentTypes.UpdateGeoZoneDTO | FulfillmentTypes.UpdateGeoZoneDTO[], - sharedContext?: Context + @MedusaContext() sharedContext: Context = {} ): Promise { const updatedGeoZones = await this.geoZoneService_.update( data, diff --git a/packages/types/src/fulfillment/mutations/fulfillment-set.ts b/packages/types/src/fulfillment/mutations/fulfillment-set.ts index 52f02b9f16508..9d707786766d4 100644 --- a/packages/types/src/fulfillment/mutations/fulfillment-set.ts +++ b/packages/types/src/fulfillment/mutations/fulfillment-set.ts @@ -3,12 +3,12 @@ import { CreateServiceZoneDTO } from "./service-zone" export interface CreateFulfillmentSetDTO { name: string type: string - service_zones?: (Omit & { - fulfillment_set?: string - })[] + service_zones?: Omit[] } -export interface UpdateFulfillmentSetDTO - extends Partial { +export interface UpdateFulfillmentSetDTO { id: string + name?: string + type?: string + service_zones?: (Omit | { id: string })[] } diff --git a/packages/types/src/fulfillment/mutations/service-zone.ts b/packages/types/src/fulfillment/mutations/service-zone.ts index 7d9e7461aa6f1..8d678acc07b0c 100644 --- a/packages/types/src/fulfillment/mutations/service-zone.ts +++ b/packages/types/src/fulfillment/mutations/service-zone.ts @@ -3,11 +3,11 @@ import { CreateGeoZoneDTO } from "./geo-zone" export interface CreateServiceZoneDTO { name: string fulfillment_set_id: string - geo_zones?: (Omit & { - service_zone_id?: string - })[] + geo_zones?: Omit[] } -export interface UpdateServiceZoneDTO extends Partial { +export interface UpdateServiceZoneDTO { id: string + name?: string + geo_zones?: (Omit | { id: string })[] } From aea52f5b262ee18898b35dea6a399401532e1920 Mon Sep 17 00:00:00 2001 From: adrien2p Date: Wed, 14 Feb 2024 13:36:41 +0100 Subject: [PATCH 28/29] cleanup --- .../services/fulfillment-module-service.ts | 135 ------------------ 1 file changed, 135 deletions(-) diff --git a/packages/fulfillment/src/services/fulfillment-module-service.ts b/packages/fulfillment/src/services/fulfillment-module-service.ts index d3b8efa630ad1..9b873872268ba 100644 --- a/packages/fulfillment/src/services/fulfillment-module-service.ts +++ b/packages/fulfillment/src/services/fulfillment-module-service.ts @@ -73,141 +73,6 @@ export default class FulfillmentModuleService< return joinerConfig } - /** - * Returns the identifier of a geo zone. The identifier is a string that is - * generated based on the type of the geo zone and the properties of the geo - * zone. The identifier is used for map building and retrieval. - * - * @param geoZone - * @param preventIdUsage - * @protected - */ - protected static getGeoZoneIdentifier( - geoZone: Partial, - object?: any // todo remove - ): string { - let identifier: string[] = [geoZone.type!] - - if (geoZone.country_code) { - identifier.push("cc:" + geoZone.country_code) - } - if (geoZone.province_code) { - identifier.push("pc:" + geoZone.province_code) - } - if (geoZone.city) { - identifier.push("c:" + geoZone.city) - } - if (geoZone.postal_expression) { - identifier.push("pe:" + JSON.stringify(geoZone.postal_expression)) - } - - return identifier.join("_") - } - - /** - * Preparation step of the fulfillment set creation. This method is responsible for - * extracting the service zones and geo zones from the data and then from that - * data extract the ids of the service zones and geo zones that are already - * existing in the database. Then it will fetch the existing service zones and - * geo zones from the database and return them. - * - * @param data - * @param sharedContext - * @protected - */ - /*protected async prepareCreateData( - data: ( - | FulfillmentTypes.CreateFulfillmentSetDTO - | FulfillmentTypes.UpdateFulfillmentSetDTO - )[], - sharedContext?: Context - ): Promise<{ - existingServiceZones: TServiceZoneEntity[] - existingServiceZonesMap: Map - existingGeoZones: TGeoZoneEntity[] - existingGeoZonesMap: Map - }> { - let serviceZoneIds: string[] = [] - let geoZoneIds: string[] = [] - - data.forEach(({ service_zones }) => { - service_zones?.forEach((serviceZone) => { - if ("id" in serviceZone) { - serviceZoneIds.push(serviceZone.id) - } - - if ("geo_zones" in serviceZone && serviceZone.geo_zones) { - serviceZone.geo_zones.forEach((geoZone) => { - if ("id" in geoZone) { - geoZoneIds.push(geoZone.id) - } - }) - } - }) - }) - - serviceZoneIds = serviceZoneIds.filter(Boolean) - geoZoneIds = geoZoneIds.filter(Boolean) - - let existingServiceZones: TServiceZoneEntity[] = [] - let existingServiceZonesMap = new Map() - let existingGeoZones: TGeoZoneEntity[] = [] - let existingGeoZonesMap = new Map() - const promises: Promise[] = [] - - if (serviceZoneIds.length) { - promises.push( - this.serviceZoneService_ - .list( - { - id: serviceZoneIds, - }, - { - select: ["id", "name"], - }, - sharedContext - ) - .then((serviceZones) => { - existingServiceZones = serviceZones - existingServiceZonesMap = new Map( - existingServiceZones.map((serviceZone) => [ - serviceZone.id, - serviceZone, - ]) - ) - }) - ) - } - - if (geoZoneIds.length) { - promises.push( - this.geoZoneService_ - .list( - { - id: geoZoneIds, - }, - {}, - sharedContext - ) - .then((geoZones) => { - existingGeoZones = geoZones - existingGeoZonesMap = new Map( - existingGeoZones.map((geoZone) => [geoZone.id, geoZone]) - ) - }) - ) - } - - await promiseAll(promises) - - return { - existingServiceZones, - existingServiceZonesMap, - existingGeoZones, - existingGeoZonesMap, - } - }*/ - create( data: FulfillmentTypes.CreateFulfillmentSetDTO[], sharedContext?: Context From eb775f2932151b72c7f947e64e509e1f85993d40 Mon Sep 17 00:00:00 2001 From: adrien2p Date: Wed, 14 Feb 2024 13:51:08 +0100 Subject: [PATCH 29/29] move get set diff --- .../services/fulfillment-module-service.ts | 2 +- .../utils/src/common/get-set-difference.ts | 19 +++++++++++++++++++ packages/utils/src/common/index.ts | 2 +- 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 packages/utils/src/common/get-set-difference.ts diff --git a/packages/fulfillment/src/services/fulfillment-module-service.ts b/packages/fulfillment/src/services/fulfillment-module-service.ts index 9b873872268ba..2dbd8de4efc48 100644 --- a/packages/fulfillment/src/services/fulfillment-module-service.ts +++ b/packages/fulfillment/src/services/fulfillment-module-service.ts @@ -15,11 +15,11 @@ import { MedusaError, ModulesSdkUtils, promiseAll, + getSetDifference } from "@medusajs/utils" import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config" import { FulfillmentSet, GeoZone, ServiceZone, ShippingOption } from "@models" -import { getSetDifference } from "@medusajs/medusa/dist/utils/diff-set" const generateMethodForModels = [ServiceZone, ShippingOption, GeoZone] diff --git a/packages/utils/src/common/get-set-difference.ts b/packages/utils/src/common/get-set-difference.ts new file mode 100644 index 0000000000000..ed3826bd4e693 --- /dev/null +++ b/packages/utils/src/common/get-set-difference.ts @@ -0,0 +1,19 @@ +/** + * Get the difference between two sets. The difference is the elements that are in the original set but not in the compare set. + * @param orignalSet + * @param compareSet + */ +export function getSetDifference( + orignalSet: Set, + compareSet: Set +): Set { + const difference = new Set() + + orignalSet.forEach((element) => { + if (!compareSet.has(element)) { + difference.add(element) + } + }) + + return difference +} diff --git a/packages/utils/src/common/index.ts b/packages/utils/src/common/index.ts index e654f2eb46213..230ce46dccb15 100644 --- a/packages/utils/src/common/index.ts +++ b/packages/utils/src/common/index.ts @@ -1,5 +1,6 @@ export * from "./alter-columns-helper" export * from "./array-difference" +export * from "./get-set-difference" export * from "./build-query" export * from "./camel-to-snake-case" export * from "./container" @@ -46,4 +47,3 @@ export * from "./to-pascal-case" export * from "./transaction" export * from "./upper-case-first" export * from "./wrap-handler" -