feat(fulfillment): Soft deletes (#6630)

**What**
- Ensure soft delete works properly according to the soft delete configuration and validate all relation of the entire data model
- Add is_enabled to the providers in order to manage new providers to enabled or disabled
- include joiner config update

FIXES CORE-1853
FIXES CORE-1830
FIXES CORE-1719
This commit is contained in:
Adrien de Peretti
2024-03-11 16:56:08 +01:00
committed by GitHub
parent 78e5ec459a
commit d9d5afc3cf
23 changed files with 985 additions and 696 deletions

View File

@@ -0,0 +1,5 @@
---
"@medusajs/types": patch
---
Feat/fulfillment module soft delete and providers enabled

View File

@@ -6,6 +6,8 @@ export function generateCreateFulfillmentData(
shipping_option_id: string
}
) {
const randomString = Math.random().toString(36).substring(7)
return {
location_id: "test-location",
packed_at: null,
@@ -17,28 +19,28 @@ export function generateCreateFulfillmentData(
shipping_option_id: data.shipping_option_id,
metadata: data.metadata ?? null,
delivery_address: data.delivery_address ?? {
address_1: "test-address",
address_2: "test-address",
city: "test-city",
postal_code: "test-postal-code",
country_code: "test-country-code",
province: "test-province",
phone: "test-phone",
full_name: "test-full-name",
address_1: "test-address_" + randomString,
address_2: "test-address_" + randomString,
city: "test-city_" + randomString,
postal_code: "test-postal-code_" + randomString,
country_code: "test-country-code_" + randomString,
province: "test-province_" + randomString,
phone: "test-phone_" + randomString,
full_name: "test-full-name_" + randomString,
},
items: data.items ?? [
{
title: "test-title",
sku: "test-sku",
title: "test-title_" + randomString,
sku: "test-sku_" + randomString,
quantity: 1,
barcode: "test-barcode",
barcode: "test-barcode_" + randomString,
},
],
labels: data.labels ?? [
{
tracking_number: "test-tracking-number",
tracking_url: "test-tracking-url",
label_url: "test-label-url",
tracking_number: "test-tracking-number_" + randomString,
tracking_url: "test-tracking-url_" + randomString,
label_url: "test-label-url_" + randomString,
},
],
order: data.order ?? {},

View File

@@ -1,2 +1,52 @@
import { generateCreateShippingOptionsData } from "./shipping-options"
import { generateCreateFulfillmentData } from "./fulfillment"
import { IFulfillmentModuleService } from "@medusajs/types"
export * from "./shipping-options"
export * from "./fulfillment"
export async function createFullDataStructure(
service: IFulfillmentModuleService,
{
providerId,
}: {
providerId: string
}
) {
const randomString = Math.random().toString(36).substring(7)
const shippingProfile = await service.createShippingProfiles({
// generate random string
name: "test_" + randomString,
type: "default",
})
const fulfillmentSet = await service.create({
name: "test_" + randomString,
type: "test-type",
})
const serviceZone = await service.createServiceZones({
name: "test_" + randomString,
fulfillment_set_id: fulfillmentSet.id,
geo_zones: [
{
type: "country",
country_code: "US_" + randomString,
},
],
})
const shippingOption = await service.createShippingOptions(
generateCreateShippingOptionsData({
fulfillment_provider_id: providerId,
service_zone_id: serviceZone.id,
shipping_profile_id: shippingProfile.id,
})
)
await service.createFulfillment(
generateCreateFulfillmentData({
provider_id: providerId,
shipping_option_id: shippingOption.id,
})
)
}

View File

@@ -14,14 +14,16 @@ export function generateCreateShippingOptionsData({
name?: string
type?: CreateShippingOptionDTO["type"]
}): Required<CreateShippingOptionDTO> {
const randomString = Math.random().toString(36).substring(7)
return {
service_zone_id: service_zone_id,
shipping_profile_id: shipping_profile_id,
fulfillment_provider_id: fulfillment_provider_id,
type: type ?? {
code: "test-type",
description: "test-description",
label: "test-label",
code: "test-type_" + randomString,
description: "test-description_" + randomString,
label: "test-label_" + randomString,
},
data: data ?? {
amount: 1000,

View File

@@ -1,14 +1,11 @@
import { resolve } from "path"
import { Modules, ModulesDefinition } from "@medusajs/modules-sdk"
import { Modules } from "@medusajs/modules-sdk"
import { IFulfillmentModuleService } from "@medusajs/types"
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
import {
generateCreateFulfillmentData,
generateCreateShippingOptionsData,
} from "../../__fixtures__"
import { initModules } from "medusa-test-utils/dist"
import { FulfillmentProviderService } from "@services"
import { FulfillmentProviderServiceFixtures } from "../../__fixtures__/providers"
jest.setTimeout(100000)
@@ -33,73 +30,7 @@ const providerId = "fixtures-fulfillment-provider_test-provider"
moduleIntegrationTestRunner({
moduleName: Modules.FULFILLMENT,
moduleOptions: moduleOptions,
testSuite: ({
MikroOrmWrapper,
service,
}: SuiteOptions<IFulfillmentModuleService>) => {
describe("Fulfillment Module Service", () => {
it("should load and save all the providers on bootstrap", async () => {
const databaseConfig = {
schema: "public",
clientUrl: MikroOrmWrapper.clientUrl,
}
const providersConfig = {}
for (let i = 0; i < 10; i++) {
providersConfig[`provider-${i}`] = {}
}
const moduleOptions = {
databaseConfig,
modulesConfig: {
[Modules.FULFILLMENT]: {
definition: ModulesDefinition[Modules.FULFILLMENT],
options: {
databaseConfig,
providers: [
{
resolve: resolve(
process.cwd() +
"/integration-tests/__fixtures__/providers/default-provider"
),
options: {
config: providersConfig,
},
},
],
},
},
},
}
const { shutdown } = await initModules(moduleOptions)
const fulfillmentProviderrs =
await MikroOrmWrapper.forkManager().execute(
`SELECT * FROM fulfillment_provider`
)
expect(fulfillmentProviderrs).toHaveLength(
Object.keys(providersConfig).length + 1 // +1 for the default provider
)
for (const [name] of Object.entries(providersConfig)) {
const provider = fulfillmentProviderrs.find((p) => {
return (
p.id ===
FulfillmentProviderService.getRegistrationIdentifier(
FulfillmentProviderServiceFixtures,
name
)
)
})
expect(provider).toBeDefined()
}
await shutdown()
})
})
testSuite: ({ service }: SuiteOptions<IFulfillmentModuleService>) => {
describe("Fulfillment Module Service", () => {
describe("read", () => {
it("should list fulfillment", async () => {

View File

@@ -0,0 +1,264 @@
import { Modules, ModulesDefinition } from "@medusajs/modules-sdk"
import { FulfillmentSetDTO, IFulfillmentModuleService } from "@medusajs/types"
import {
initModules,
moduleIntegrationTestRunner,
SuiteOptions,
} from "medusa-test-utils/dist"
import { resolve } from "path"
import { createFullDataStructure } from "../../__fixtures__"
import { FulfillmentProviderService } from "@services"
import { FulfillmentProviderServiceFixtures } from "../../__fixtures__/providers"
let moduleOptions = {
providers: [
{
resolve: resolve(
process.cwd() +
"/integration-tests/__fixtures__/providers/default-provider"
),
options: {
config: {
"test-provider": {},
},
},
},
],
}
let providerId = "fixtures-fulfillment-provider_test-provider"
async function list(
service: IFulfillmentModuleService,
...args: Parameters<IFulfillmentModuleService["list"]>
) {
const [filters = {}, config = {}] = args
const finalConfig = {
relations: [
"service_zones.geo_zones",
"service_zones.shipping_options.shipping_profile",
"service_zones.shipping_options.fulfillment_provider",
"service_zones.shipping_options.type",
"service_zones.shipping_options.rules",
"service_zones.shipping_options.fulfillments.labels",
"service_zones.shipping_options.fulfillments.items",
"service_zones.shipping_options.fulfillments.delivery_address",
],
...config,
}
return await service.list(filters, finalConfig)
}
function expectSoftDeleted(
fulfillmentSets: FulfillmentSetDTO[],
{ softDeleted = false } = {}
) {
expect(fulfillmentSets).toHaveLength(1)
let fulfillmentSet = fulfillmentSets[0]
expect(!!fulfillmentSet.deleted_at).toEqual(softDeleted)
expect(fulfillmentSet.service_zones).toHaveLength(1)
let serviceZone = fulfillmentSet.service_zones[0]
expect(!!serviceZone.deleted_at).toEqual(softDeleted)
expect(serviceZone.geo_zones).toHaveLength(1)
expect(serviceZone.shipping_options).toHaveLength(1)
let geoZone = serviceZone.geo_zones[0]
expect(!!geoZone.deleted_at).toEqual(softDeleted)
let shippingOption = serviceZone.shipping_options[0]
expect(!!shippingOption.deleted_at).toEqual(softDeleted)
expect(!!shippingOption.shipping_profile.deleted_at).toEqual(false)
expect(!!shippingOption.type.deleted_at).toEqual(softDeleted)
expect(shippingOption.fulfillments).toHaveLength(1)
expect(shippingOption.rules).toHaveLength(1)
let rule = shippingOption.rules[0]
expect(!!rule.deleted_at).toEqual(softDeleted)
/**
* We do not expect the fulfillment to be soft deleted when soft deleting parents entities
*/
let fulfillment = shippingOption.fulfillments[0]
expect(!!fulfillment.deleted_at).toEqual(false)
expect(fulfillment.labels).toHaveLength(1)
expect(fulfillment.items).toHaveLength(1)
let label = fulfillment.labels[0]
expect(!!label.deleted_at).toEqual(false)
let item = fulfillment.items[0]
expect(!!item.deleted_at).toEqual(false)
let deliveryAddress = fulfillment.delivery_address
expect(!!deliveryAddress.deleted_at).toEqual(false)
}
moduleIntegrationTestRunner({
moduleName: Modules.FULFILLMENT,
moduleOptions,
testSuite: ({
MikroOrmWrapper,
service,
}: SuiteOptions<IFulfillmentModuleService>) =>
describe("Fulfillment Module Service", () => {
it("should load and save all the providers on bootstrap with the correct is_enabled value", async () => {
const databaseConfig = {
schema: "public",
clientUrl: MikroOrmWrapper.clientUrl,
}
const providersConfig = {}
for (let i = 0; i < 10; i++) {
providersConfig[`provider-${i}`] = {}
}
let moduleOptions = {
databaseConfig,
modulesConfig: {
[Modules.FULFILLMENT]: {
definition: ModulesDefinition[Modules.FULFILLMENT],
options: {
databaseConfig,
providers: [
{
resolve: resolve(
process.cwd() +
"/integration-tests/__fixtures__/providers/default-provider"
),
options: {
config: providersConfig,
},
},
],
},
},
},
}
let { shutdown } = await initModules(moduleOptions)
let fulfillmentProviders = await MikroOrmWrapper.forkManager().execute(
`SELECT * FROM fulfillment_provider`
)
expect(fulfillmentProviders).toHaveLength(
Object.keys(providersConfig).length + 1 // +1 for the default provider
)
for (const [name] of Object.entries(providersConfig)) {
const provider = fulfillmentProviders.find((p) => {
return (
p.id ===
FulfillmentProviderService.getRegistrationIdentifier(
FulfillmentProviderServiceFixtures,
name
)
)
})
expect(provider).toBeDefined()
expect(provider.is_enabled).toBeTruthy()
}
await shutdown()
const providersConfig2 = {}
for (let i = 10; i < 20; i++) {
providersConfig2[`provider-${i}`] = {}
}
moduleOptions = {
databaseConfig,
modulesConfig: {
[Modules.FULFILLMENT]: {
definition: ModulesDefinition[Modules.FULFILLMENT],
options: {
databaseConfig,
providers: [
{
resolve: resolve(
process.cwd() +
"/integration-tests/__fixtures__/providers/default-provider"
),
options: {
config: providersConfig2,
},
},
],
},
},
},
}
const medusaApp = await initModules(moduleOptions)
shutdown = medusaApp.shutdown
fulfillmentProviders = await MikroOrmWrapper.forkManager().execute(
`SELECT * FROM fulfillment_provider`
)
expect(fulfillmentProviders).toHaveLength(
Object.keys(providersConfig2).length +
Object.keys(providersConfig).length +
1 // +1 for the default provider
)
const allProviders = Object.assign(
{},
providersConfig,
providersConfig2
)
for (const [name] of Object.entries(allProviders)) {
const provider = fulfillmentProviders.find((p) => {
return (
p.id ===
FulfillmentProviderService.getRegistrationIdentifier(
FulfillmentProviderServiceFixtures,
name
)
)
})
expect(provider).toBeDefined()
const isEnabled = !!providersConfig2[name]
expect(provider.is_enabled).toEqual(isEnabled)
}
await shutdown().catch(() => void 0)
})
it("should soft delete and restore the data respecting the configured cascade", async () => {
await createFullDataStructure(service, { providerId })
let fulfillmentSets = await list(service)
expectSoftDeleted(fulfillmentSets)
/**
* Soft delete the fulfillment set
*/
await service.softDelete(fulfillmentSets[0].id)
const deletedFulfillmentSets = await list(
service,
{},
{
withDeleted: true,
}
)
expectSoftDeleted(deletedFulfillmentSets, { softDeleted: true })
/**
* Restore the fulfillment set
*/
await service.restore(fulfillmentSets[0].id)
const restoredFulfillmentSets = await list(service)
expectSoftDeleted(restoredFulfillmentSets)
})
}),
})

View File

@@ -1,10 +1,20 @@
import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types"
import { MapToConfig } from "@medusajs/utils"
import {
Fulfillment,
FulfillmentSet,
GeoZone,
ServiceZone,
ShippingOption,
ShippingProfile,
} from "@models"
// TODO manage the config
export const LinkableKeys: Record<string, string> = {}
export const LinkableKeys: Record<string, string> = {
fulfillment_id: Fulfillment.name,
fulfillment_set_id: FulfillmentSet.name,
shipping_option_id: ShippingOption.name,
}
const entityLinkableKeysMap: MapToConfig = {}
Object.entries(LinkableKeys).forEach(([key, value]) => {
@@ -21,5 +31,47 @@ export const joinerConfig: ModuleJoinerConfig = {
serviceName: Modules.FULFILLMENT,
primaryKeys: ["id"],
linkableKeys: LinkableKeys,
alias: [],
alias: [
{
name: ["fulfillment_set", "fulfillment_sets"],
args: {
entity: FulfillmentSet.name,
},
},
{
name: ["shipping_option", "shipping_options"],
args: {
entity: ShippingOption.name,
methodSuffix: "ShippingOptions",
},
},
{
name: ["shipping_profile", "shipping_profiles"],
args: {
entity: ShippingProfile.name,
methodSuffix: "ShippingProfiles",
},
},
{
name: ["fulfillment", "fulfillments"],
args: {
entity: Fulfillment.name,
methodSuffix: "Fulfillments",
},
},
{
name: ["service_zone", "service_zones"],
args: {
entity: ServiceZone.name,
methodSuffix: "ServiceZones",
},
},
{
name: ["geo_zone", "geo_zones"],
args: {
entity: GeoZone.name,
methodSuffix: "GeoZones",
},
},
],
} as ModuleJoinerConfig

View File

@@ -2,7 +2,7 @@ import { moduleProviderLoader } from "@medusajs/modules-sdk"
import { LoaderOptions, ModuleProvider, ModulesSdkTypes } from "@medusajs/types"
import { asFunction, asValue, Lifetime } from "awilix"
import { FulfillmentIdentifiersRegistrationName } from "@types"
import { lowerCaseFirst } from "@medusajs/utils"
import { lowerCaseFirst, promiseAll } from "@medusajs/utils"
import { FulfillmentProviderService } from "@services"
import { ContainerRegistrationKeys } from "@medusajs/utils/src"
@@ -56,7 +56,8 @@ async function syncDatabaseProviders({ container }) {
FulfillmentProviderService.name
)
const logger = container.resolve(ContainerRegistrationKeys.LOGGER)
const logger = container.resolve(ContainerRegistrationKeys.LOGGER) ?? console
try {
const providerIdentifiers: string[] = (
container.resolve(FulfillmentIdentifiersRegistrationName) ?? []
@@ -65,22 +66,44 @@ async function syncDatabaseProviders({ container }) {
const providerService: ModulesSdkTypes.InternalModuleService<any> =
container.resolve(providerServiceRegistrationKey)
const providers = await providerService.list({
id: providerIdentifiers,
})
const providers = await providerService.list({})
const loadedProvidersMap = new Map(providers.map((p) => [p.id, p]))
const providersToCreate: any[] = []
for (const identifier of providerIdentifiers) {
if (loadedProvidersMap.has(identifier)) {
continue
}
const providersToCreate = providerIdentifiers.filter(
(id) => !loadedProvidersMap.has(id)
)
const providersToEnabled = providerIdentifiers.filter((id) =>
loadedProvidersMap.has(id)
)
const providersToDisable = providers.filter(
(p) => !providerIdentifiers.includes(p.id)
)
providersToCreate.push({ id: identifier })
const promises: Promise<any>[] = []
if (providersToCreate.length) {
promises.push(
providerService.create(providersToCreate.map((id) => ({ id })))
)
}
await providerService.create(providersToCreate)
if (providersToEnabled.length) {
promises.push(
providerService.update(
providersToEnabled.map((id) => ({ id, is_enabled: true }))
)
)
}
if (providersToDisable.length) {
promises.push(
providerService.update(
providersToDisable.map((p) => ({ id: p.id, is_enabled: false }))
)
)
}
await promiseAll(promises)
} catch (error) {
logger.error(`Error syncing providers: ${error.message}`)
}

View File

@@ -184,61 +184,20 @@
"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",
"is_enabled": {
"name": "is_enabled",
"type": "boolean",
"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"
"default": "true",
"mappedType": "boolean"
}
},
"name": "fulfillment_provider",
"schema": "public",
"indexes": [
{
"keyName": "IDX_fulfillment_provider_deleted_at",
"columnNames": [
"deleted_at"
],
"composite": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_fulfillment_provider_deleted_at\" ON \"fulfillment_provider\" (deleted_at) WHERE deleted_at IS NOT NULL"
},
{
"keyName": "fulfillment_provider_pkey",
"columnNames": [
@@ -485,6 +444,7 @@
"id"
],
"referencedTableName": "public.fulfillment_set",
"deleteRule": "cascade",
"updateRule": "cascade"
}
}
@@ -678,6 +638,7 @@
"id"
],
"referencedTableName": "public.service_zone",
"deleteRule": "cascade",
"updateRule": "cascade"
}
}
@@ -971,7 +932,7 @@
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"nullable": false,
"mappedType": "text"
},
"created_at": {
@@ -1091,6 +1052,7 @@
"id"
],
"referencedTableName": "public.service_zone",
"deleteRule": "cascade",
"updateRule": "cascade"
},
"shipping_option_shipping_profile_id_foreign": {
@@ -1269,6 +1231,7 @@
"id"
],
"referencedTableName": "public.shipping_option",
"deleteRule": "cascade",
"updateRule": "cascade"
}
}
@@ -1348,7 +1311,7 @@
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"nullable": true,
"mappedType": "text"
},
"shipping_option_id": {
@@ -1375,7 +1338,7 @@
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"nullable": true,
"mappedType": "text"
},
"created_at": {
@@ -1485,6 +1448,7 @@
"id"
],
"referencedTableName": "public.fulfillment_provider",
"deleteRule": "set null",
"updateRule": "cascade"
},
"fulfillment_shipping_option_id_foreign": {
@@ -1510,6 +1474,7 @@
"id"
],
"referencedTableName": "public.fulfillment_address",
"deleteRule": "cascade",
"updateRule": "cascade"
}
}

View File

@@ -1,13 +1,12 @@
import { Migration } from '@mikro-orm/migrations';
export class Migration20240305095931_InitialSetupMigration extends Migration {
export class Migration20240311145700_InitialSetupMigration extends Migration {
async up(): Promise<void> {
this.addSql('create table if not exists "fulfillment_address" ("id" text not null, "company" text null, "first_name" text null, "last_name" text null, "address_1" text null, "address_2" text null, "city" text null, "country_code" text null, "province" text null, "postal_code" text null, "phone" text null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_address_pkey" primary key ("id"));');
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_address_deleted_at" ON "fulfillment_address" (deleted_at) WHERE deleted_at IS NOT NULL;');
this.addSql('create table if not exists "fulfillment_provider" ("id" text not null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_provider_pkey" primary key ("id"));');
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_provider_deleted_at" ON "fulfillment_provider" (deleted_at) WHERE deleted_at IS NOT NULL;');
this.addSql('create table if not exists "fulfillment_provider" ("id" text not null, "is_enabled" boolean not null default true, constraint "fulfillment_provider_pkey" primary key ("id"));');
this.addSql('create table if not exists "fulfillment_set" ("id" text not null, "name" text not null, "type" text not null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_set_pkey" primary key ("id"));');
this.addSql('CREATE UNIQUE INDEX IF NOT EXISTS "IDX_fulfillment_set_name_unique" ON "fulfillment_set" (name) WHERE deleted_at IS NULL;');
@@ -32,7 +31,7 @@ export class Migration20240305095931_InitialSetupMigration extends Migration {
this.addSql('CREATE UNIQUE INDEX IF NOT EXISTS "IDX_shipping_profile_name_unique" ON "shipping_profile" (name) WHERE deleted_at IS NULL;');
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_profile_deleted_at" ON "shipping_profile" (deleted_at) WHERE deleted_at IS NOT NULL;');
this.addSql('create table if not exists "shipping_option" ("id" text not null, "name" text not null, "price_type" text check ("price_type" in (\'calculated\', \'flat\')) not null default \'calculated\', "service_zone_id" text not null, "shipping_profile_id" text null, "fulfillment_provider_id" text null, "data" jsonb null, "metadata" jsonb null, "shipping_option_type_id" text 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('create table if not exists "shipping_option" ("id" text not null, "name" text not null, "price_type" text check ("price_type" in (\'calculated\', \'flat\')) not null default \'calculated\', "service_zone_id" text not null, "shipping_profile_id" text null, "fulfillment_provider_id" text null, "data" jsonb null, "metadata" jsonb null, "shipping_option_type_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "shipping_option_pkey" primary key ("id"));');
this.addSql('alter table if exists "shipping_option" add constraint "shipping_option_shipping_option_type_id_unique" unique ("shipping_option_type_id");');
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_service_zone_id" ON "shipping_option" (service_zone_id) WHERE deleted_at IS NULL;');
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_shipping_profile_id" ON "shipping_option" (shipping_profile_id) WHERE deleted_at IS NULL;');
@@ -44,7 +43,7 @@ export class Migration20240305095931_InitialSetupMigration extends Migration {
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_rule_shipping_option_id" ON "shipping_option_rule" (shipping_option_id) WHERE deleted_at IS NULL;');
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_rule_deleted_at" ON "shipping_option_rule" (deleted_at) WHERE deleted_at IS NOT NULL;');
this.addSql('create table if not exists "fulfillment" ("id" text not null, "location_id" text not null, "packed_at" timestamptz null, "shipped_at" timestamptz null, "delivered_at" timestamptz null, "canceled_at" timestamptz null, "data" jsonb null, "provider_id" text not null, "shipping_option_id" text null, "metadata" jsonb null, "delivery_address_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 table if not exists "fulfillment" ("id" text not null, "location_id" text not null, "packed_at" timestamptz null, "shipped_at" timestamptz null, "delivered_at" timestamptz null, "canceled_at" timestamptz null, "data" jsonb null, "provider_id" text null, "shipping_option_id" text null, "metadata" jsonb null, "delivery_address_id" text null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_pkey" primary key ("id"));');
this.addSql('alter table if exists "fulfillment" add constraint "fulfillment_delivery_address_id_unique" unique ("delivery_address_id");');
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_location_id" ON "fulfillment" (location_id) WHERE deleted_at IS NULL;');
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_provider_id" ON "fulfillment" (provider_id) WHERE deleted_at IS NULL;');
@@ -61,20 +60,20 @@ export class Migration20240305095931_InitialSetupMigration 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" 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 "service_zone" add constraint "service_zone_fulfillment_set_id_foreign" foreign key ("fulfillment_set_id") references "fulfillment_set" ("id") on update cascade on delete cascade;');
this.addSql('alter table if exists "geo_zone" add constraint "geo_zone_service_zone_id_foreign" foreign key ("service_zone_id") references "service_zone" ("id") on update cascade;');
this.addSql('alter table if exists "geo_zone" add constraint "geo_zone_service_zone_id_foreign" foreign key ("service_zone_id") references "service_zone" ("id") on update cascade on delete cascade;');
this.addSql('alter table if exists "shipping_option" add constraint "shipping_option_service_zone_id_foreign" foreign key ("service_zone_id") references "service_zone" ("id") on update cascade;');
this.addSql('alter table if exists "shipping_option" add constraint "shipping_option_service_zone_id_foreign" foreign key ("service_zone_id") references "service_zone" ("id") on update cascade on delete cascade;');
this.addSql('alter table if exists "shipping_option" add constraint "shipping_option_shipping_profile_id_foreign" foreign key ("shipping_profile_id") references "shipping_profile" ("id") on update cascade on delete set null;');
this.addSql('alter table if exists "shipping_option" add constraint "shipping_option_fulfillment_provider_id_foreign" foreign key ("fulfillment_provider_id") references "fulfillment_provider" ("id") on update cascade on delete set null;');
this.addSql('alter table if exists "shipping_option" add constraint "shipping_option_shipping_option_type_id_foreign" foreign key ("shipping_option_type_id") references "shipping_option_type" ("id") on update cascade on delete cascade;');
this.addSql('alter table if exists "shipping_option_rule" add constraint "shipping_option_rule_shipping_option_id_foreign" foreign key ("shipping_option_id") references "shipping_option" ("id") on update cascade;');
this.addSql('alter table if exists "shipping_option_rule" add constraint "shipping_option_rule_shipping_option_id_foreign" foreign key ("shipping_option_id") references "shipping_option" ("id") on update cascade on delete cascade;');
this.addSql('alter table if exists "fulfillment" add constraint "fulfillment_provider_id_foreign" foreign key ("provider_id") references "fulfillment_provider" ("id") on update cascade;');
this.addSql('alter table if exists "fulfillment" add constraint "fulfillment_provider_id_foreign" foreign key ("provider_id") references "fulfillment_provider" ("id") on update cascade on delete set null;');
this.addSql('alter table if exists "fulfillment" add constraint "fulfillment_shipping_option_id_foreign" foreign key ("shipping_option_id") references "shipping_option" ("id") on update cascade on delete set null;');
this.addSql('alter table if exists "fulfillment" add constraint "fulfillment_delivery_address_id_foreign" foreign key ("delivery_address_id") references "fulfillment_address" ("id") on update cascade;');
this.addSql('alter table if exists "fulfillment" add constraint "fulfillment_delivery_address_id_foreign" foreign key ("delivery_address_id") references "fulfillment_address" ("id") on update cascade on delete cascade;');
this.addSql('alter table if exists "fulfillment_label" add constraint "fulfillment_label_fulfillment_id_foreign" foreign key ("fulfillment_id") references "fulfillment" ("id") on update cascade on delete cascade;');

View File

@@ -1,66 +1,19 @@
import {
createPsqlIndexStatementHelper,
DALUtils,
generateEntityId,
} from "@medusajs/utils"
import { DAL } from "@medusajs/types"
import { generateEntityId } from "@medusajs/utils"
import {
BeforeCreate,
Collection,
Entity,
Filter,
OneToMany,
OnInit,
OptionalProps,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import ShippingOption from "./shipping-option"
type FulfillmentProviderOptionalProps = DAL.SoftDeletableEntityDateColumns
const DeletedAtIndex = createPsqlIndexStatementHelper({
tableName: "fulfillment_provider",
columns: "deleted_at",
where: "deleted_at IS NOT NULL",
})
@Entity()
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
export default class FulfillmentProvider {
[OptionalProps]?: FulfillmentProviderOptionalProps
@PrimaryKey({ columnType: "text" })
id: string
@Property({ columnType: "jsonb", nullable: true })
metadata: Record<string, unknown> | null = null
@OneToMany(
() => ShippingOption,
(shippingOption) => shippingOption.fulfillment_provider
)
shipping_options = new Collection<ShippingOption>(this)
@Property({
onCreate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
created_at: Date
@Property({
onCreate: () => new Date(),
onUpdate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
updated_at: Date
@DeletedAtIndex.MikroORMIndex()
@Property({ columnType: "timestamptz", nullable: true })
deleted_at: Date | null = null
@Property({ columnType: "boolean", defaultRaw: "true" })
is_enabled: boolean = true
@BeforeCreate()
onCreate() {

View File

@@ -7,6 +7,7 @@ import {
import { DAL } from "@medusajs/types"
import {
BeforeCreate,
Cascade,
Collection,
Entity,
Filter,
@@ -93,6 +94,8 @@ export default class Fulfillment {
columnType: "text",
fieldName: "provider_id",
mapToPk: true,
nullable: true,
onDelete: "set null",
})
@FulfillmentProviderIdIndex.MikroORMIndex()
provider_id: string
@@ -102,6 +105,7 @@ export default class Fulfillment {
fieldName: "shipping_option_id",
nullable: true,
mapToPk: true,
onDelete: "set null",
})
@FulfillmentShippingOptionIdIndex.MikroORMIndex()
shipping_option_id: string | null = null
@@ -115,13 +119,25 @@ export default class Fulfillment {
@ManyToOne(() => FulfillmentProvider, { persist: false })
provider: FulfillmentProvider
@OneToOne()
@OneToOne({
entity: () => Address,
owner: true,
cascade: [Cascade.PERSIST, "soft-remove"] as any,
nullable: true,
onDelete: "cascade",
})
delivery_address!: Address
@OneToMany(() => FulfillmentItem, (item) => item.fulfillment)
@OneToMany(() => FulfillmentItem, (item) => item.fulfillment, {
cascade: [Cascade.PERSIST, "soft-remove"] as any,
orphanRemoval: true,
})
items = new Collection<FulfillmentItem>(this)
@OneToMany(() => FulfillmentLabel, (label) => label.fulfillment)
@OneToMany(() => FulfillmentLabel, (label) => label.fulfillment, {
cascade: [Cascade.PERSIST, "soft-remove"] as any,
orphanRemoval: true,
})
labels = new Collection<FulfillmentLabel>(this)
@Property({

View File

@@ -78,11 +78,11 @@ export default class GeoZone {
type: "text",
mapToPk: true,
fieldName: "service_zone_id",
onDelete: "cascade",
})
@ServiceZoneIdIndex.MikroORMIndex()
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<string, unknown> | null = null

View File

@@ -65,6 +65,7 @@ export default class ServiceZone {
type: "text",
mapToPk: true,
fieldName: "fulfillment_set_id",
onDelete: "cascade",
})
@FulfillmentSetIdIndex.MikroORMIndex()
fulfillment_set_id: string
@@ -80,7 +81,11 @@ export default class ServiceZone {
@OneToMany(
() => ShippingOption,
(shippingOption) => shippingOption.service_zone
(shippingOption) => shippingOption.service_zone,
{
cascade: [Cascade.PERSIST, "soft-remove"] as any,
orphanRemoval: true,
}
)
shipping_options = new Collection<ShippingOption>(this)

View File

@@ -57,6 +57,7 @@ export default class ShippingOptionRule {
type: "text",
mapToPk: true,
fieldName: "shipping_option_id",
onDelete: "cascade",
})
@ShippingOptionIdIndex.MikroORMIndex()
shipping_option_id: string

View File

@@ -50,6 +50,7 @@ export default class ShippingOptionType {
@OneToOne(() => ShippingOption, (so) => so.type, {
type: "text",
onDelete: "cascade",
})
shipping_option: ShippingOption

View File

@@ -81,6 +81,7 @@ export default class ShippingOption {
type: "text",
fieldName: "service_zone_id",
mapToPk: true,
onDelete: "cascade",
})
@ServiceZoneIdIndex.MikroORMIndex()
service_zone_id: string
@@ -90,6 +91,7 @@ export default class ShippingOption {
fieldName: "shipping_profile_id",
mapToPk: true,
nullable: true,
onDelete: "set null",
})
@ShippingProfileIdIndex.MikroORMIndex()
shipping_profile_id: string | null
@@ -128,9 +130,10 @@ export default class ShippingOption {
@OneToOne(() => ShippingOptionType, (so) => so.shipping_option, {
owner: true,
cascade: [Cascade.PERSIST, Cascade.REMOVE, "soft-remove"] as any,
cascade: [Cascade.PERSIST, "soft-remove"] as any,
orphanRemoval: true,
fieldName: "shipping_option_type_id",
onDelete: "cascade",
})
type: ShippingOptionType

View File

@@ -1,6 +1,6 @@
import { ShippingOptionDTO } from "./shipping-option"
export interface ServiceProviderDTO {
export interface FulfillmentProviderDTO {
id: string
name: string
metadata: Record<string, unknown> | null

View File

@@ -1,5 +1,5 @@
import { ShippingOptionDTO } from "./shipping-option"
import { ServiceProviderDTO } from "./service-provider"
import { FulfillmentProviderDTO } from "./fulfillment-provider"
import { FulfillmentAddressDTO } from "./address"
import { FulfillmentItemDTO } from "./fulfillment-item"
import { FulfillmentLabelDTO } from "./fulfillment-label"
@@ -17,7 +17,7 @@ export interface FulfillmentDTO {
shipping_option_id: string | null
metadata: Record<string, unknown> | null
shipping_option: ShippingOptionDTO | null
provider: ServiceProviderDTO
provider: FulfillmentProviderDTO
delivery_address: FulfillmentAddressDTO
items: FulfillmentItemDTO[]
labels: FulfillmentLabelDTO[]

View File

@@ -6,7 +6,7 @@ export * from "./shipping-option"
export * from "./shipping-option-type"
export * from "./service-zone"
export * from "./geo-zone"
export * from "./service-provider"
export * from "./fulfillment-provider"
export * from "./fulfillment"
export * from "./fulfillment-item"
export * from "./fulfillment-label"

View File

@@ -1,6 +1,6 @@
import { FilterableServiceZoneProps, ServiceZoneDTO } from "./service-zone"
import { ShippingProfileDTO } from "./shipping-profile"
import { ServiceProviderDTO } from "./service-provider"
import { FulfillmentProviderDTO } from "./fulfillment-provider"
import {
FilterableShippingOptionTypeProps,
ShippingOptionTypeDTO,
@@ -10,6 +10,7 @@ import {
ShippingOptionRuleDTO,
} from "./shipping-option-rule"
import { BaseFilterable, OperatorMap } from "../../dal"
import { FulfillmentDTO } from "./fulfillment"
export type ShippingOptionPriceType = "calculated" | "flat"
@@ -19,15 +20,16 @@ export interface ShippingOptionDTO {
price_type: ShippingOptionPriceType
service_zone_id: string
shipping_profile_id: string
service_provider_id: string
fulfillment_provider_id: string
shipping_option_type_id: string | null
data: Record<string, unknown> | null
metadata: Record<string, unknown> | null
service_zone: ServiceZoneDTO
shipping_profile: ShippingProfileDTO
service_provider: ServiceProviderDTO
shipping_option_type: ShippingOptionTypeDTO
fulfillment_provider: FulfillmentProviderDTO
type: ShippingOptionTypeDTO
rules: ShippingOptionRuleDTO[]
fulfillments: FulfillmentDTO[]
created_at: Date
updated_at: Date
deleted_at: Date | null

View File

@@ -7,7 +7,7 @@ export interface CreateShippingOptionDTO {
price_type: ShippingOptionPriceType
service_zone_id: string
shipping_profile_id: string
service_provider_id: string
fulfillment_provider_id: string
type: Omit<CreateShippingOptionTypeDTO, "shipping_option_id">
data?: Record<string, unknown> | null
rules?: Omit<CreateShippingOptionRuleDTO, "shipping_option_id">[]
@@ -19,7 +19,7 @@ export interface UpdateShippingOptionDTO {
price_type?: ShippingOptionPriceType
service_zone_id?: string
shipping_profile_id?: string
service_provider_id?: string
fulfillment_provider_id?: string
type: Omit<CreateShippingOptionTypeDTO, "shipping_option_id"> | { id: string }
data?: Record<string, unknown> | null
rules?: (

File diff suppressed because it is too large Load Diff