chore(): Reorganize modules (#7210)

**What**
Move all modules to the modules directory
This commit is contained in:
Adrien de Peretti
2024-05-02 17:33:34 +02:00
committed by GitHub
parent 7a351eef09
commit 4eae25e1ef
870 changed files with 91 additions and 62 deletions

View File

@@ -0,0 +1,14 @@
import { moduleDefinition } from "./module-definition"
import { initializeFactory, Modules } from "@medusajs/modules-sdk"
export * from "./types"
export * from "./models"
export * from "./services"
export const initialize = initializeFactory({
moduleName: Modules.FULFILLMENT,
moduleDefinition,
})
export const runMigrations = moduleDefinition.runMigrations
export const revertMigration = moduleDefinition.revertMigration
export default moduleDefinition

View File

@@ -0,0 +1,94 @@
import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types"
import { MapToConfig } from "@medusajs/utils"
import {
Fulfillment,
FulfillmentProvider,
FulfillmentSet,
GeoZone,
ServiceZone,
ShippingOption,
ShippingOptionRule,
ShippingProfile,
} from "@models"
export const LinkableKeys: Record<string, string> = {
fulfillment_id: Fulfillment.name,
fulfillment_set_id: FulfillmentSet.name,
shipping_option_id: ShippingOption.name,
shipping_option_rule_id: ShippingOptionRule.name,
}
const entityLinkableKeysMap: MapToConfig = {}
Object.entries(LinkableKeys).forEach(([key, value]) => {
entityLinkableKeysMap[value] ??= []
entityLinkableKeysMap[value].push({
mapTo: key,
valueFrom: key.split("_").pop()!,
})
})
export const entityNameToLinkableKeysMap: MapToConfig = entityLinkableKeysMap
export const joinerConfig: ModuleJoinerConfig = {
serviceName: Modules.FULFILLMENT,
primaryKeys: ["id"],
linkableKeys: LinkableKeys,
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: ["fulfillment_provider", "fulfillment_providers"],
args: {
entity: FulfillmentProvider.name,
methodSuffix: "FulfillmentProviders",
},
},
{
name: ["service_zone", "service_zones"],
args: {
entity: ServiceZone.name,
methodSuffix: "ServiceZones",
},
},
{
name: ["geo_zone", "geo_zones"],
args: {
entity: GeoZone.name,
methodSuffix: "GeoZones",
},
},
{
name: ["shipping_option_rule", "shipping_option_rules"],
args: {
entity: ShippingOptionRule.name,
methodSuffix: "ShippingOptionRules",
},
},
],
} as ModuleJoinerConfig

View File

@@ -0,0 +1,113 @@
import { moduleProviderLoader } from "@medusajs/modules-sdk"
import { LoaderOptions, ModuleProvider, ModulesSdkTypes } from "@medusajs/types"
import {
ContainerRegistrationKeys,
lowerCaseFirst,
promiseAll,
} from "@medusajs/utils"
import { FulfillmentProviderService } from "@services"
import { FulfillmentIdentifiersRegistrationName } from "@types"
import { Lifetime, asFunction, asValue } from "awilix"
const registrationFn = async (klass, container, pluginOptions) => {
Object.entries(pluginOptions.config || []).map(([name, config]) => {
const key = FulfillmentProviderService.getRegistrationIdentifier(
klass,
name
)
container.register({
["fp_" + key]: asFunction((cradle) => new klass(cradle, config), {
lifetime: klass.LIFE_TIME || Lifetime.SINGLETON,
}),
})
container.registerAdd(FulfillmentIdentifiersRegistrationName, asValue(key))
})
}
export default async ({
container,
options,
}: LoaderOptions<
(
| ModulesSdkTypes.ModuleServiceInitializeOptions
| ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions
) & { providers: ModuleProvider[] }
>): Promise<void> => {
container.registerAdd(
FulfillmentIdentifiersRegistrationName,
asValue(undefined)
)
// Local providers
// TODO
await moduleProviderLoader({
container,
providers: options?.providers || [],
registerServiceFn: registrationFn,
})
await syncDatabaseProviders({
container,
})
}
async function syncDatabaseProviders({ container }) {
const providerServiceRegistrationKey = lowerCaseFirst(
FulfillmentProviderService.name
)
const logger = container.resolve(ContainerRegistrationKeys.LOGGER) ?? console
try {
const providerIdentifiers: string[] = (
container.resolve(FulfillmentIdentifiersRegistrationName) ?? []
).filter(Boolean)
const providerService: ModulesSdkTypes.InternalModuleService<any> =
container.resolve(providerServiceRegistrationKey)
const providers = await providerService.list({})
const loadedProvidersMap = new Map(providers.map((p) => [p.id, p]))
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)
)
const promises: Promise<any>[] = []
if (providersToCreate.length) {
promises.push(
providerService.create(providersToCreate.map((id) => ({ id })))
)
}
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 the fulfillment providers: ${error.message}`)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,603 @@
import { Migration } from "@mikro-orm/migrations"
export class Migration20240311145700_InitialSetupMigration extends Migration {
async up(): Promise<void> {
const shippingOptionTable = await this.execute(
`SELECT * FROM information_schema.tables where table_name = 'shipping_option' and table_schema = 'public';`
)
if (!shippingOptionTable.length) {
await migrateUpModuleMigration.call(this)
} else {
await migrateUpBackwardCompatibility.call(this)
}
}
}
/**
* This migration is for the initial setup of the fulfillment module in the case of
* an already existing database. It will check if the `shipping_option` table exists and in that case
* assume that all other associated tables exists and will migrate them all.
*
* This migration does not take into account the data migration part which
* should be handled separately. Also, after the data migration
* the tables should be cleaned up from the old columns.
**/
async function migrateUpBackwardCompatibility(
this: Migration20240311145700_InitialSetupMigration
) {
this.addSql(
'create table if not exists "fulfillment_address" ("id" text not null, "company" text null, "first_name" text null, "last_name" text null, "address_1" text null, "address_2" text null, "city" text null, "country_code" text null, "province" text null, "postal_code" text null, "phone" text null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_address_pkey" primary key ("id"));'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_address_deleted_at" ON "fulfillment_address" (deleted_at) WHERE deleted_at IS NOT NULL;'
)
this.addSql(`
alter table fulfillment_provider
alter column id type text using id::text;
alter table fulfillment_provider
add is_enabled boolean default true not null;
`)
this.addSql(
'create table if not exists "fulfillment_set" ("id" text not null, "name" text not null, "type" text not null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_set_pkey" primary key ("id"));'
)
this.addSql(
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_fulfillment_set_name_unique" ON "fulfillment_set" (name) WHERE deleted_at IS NULL;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_set_deleted_at" ON "fulfillment_set" (deleted_at) WHERE deleted_at IS NOT NULL;'
)
this.addSql(
'create table if not exists "service_zone" ("id" text not null, "name" text not null, "metadata" jsonb null, "fulfillment_set_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "service_zone_pkey" primary key ("id"));'
)
this.addSql(
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_service_zone_name_unique" ON "service_zone" (name) WHERE deleted_at IS NULL;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_service_zone_fulfillment_set_id" ON "service_zone" (fulfillment_set_id) WHERE deleted_at IS NULL;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_service_zone_deleted_at" ON "service_zone" (deleted_at) WHERE deleted_at IS NOT NULL;'
)
this.addSql(
'create table if not exists "geo_zone" ("id" text not null, "type" text check ("type" in (\'country\', \'province\', \'city\', \'zip\')) not null default \'country\', "country_code" text not null, "province_code" text null, "city" text null, "service_zone_id" text not null, "postal_expression" jsonb null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "geo_zone_pkey" primary key ("id"));'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_geo_zone_country_code" ON "geo_zone" (country_code) WHERE deleted_at IS NULL;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_geo_zone_province_code" ON "geo_zone" (province_code) WHERE deleted_at IS NULL AND province_code IS NOT NULL;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_geo_zone_city" ON "geo_zone" (city) WHERE deleted_at IS NULL AND city IS NOT NULL;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_geo_zone_service_zone_id" ON "geo_zone" (service_zone_id) WHERE deleted_at IS NULL;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_geo_zone_deleted_at" ON "geo_zone" (deleted_at) WHERE deleted_at IS NOT NULL;'
)
this.addSql(
'create table if not exists "shipping_option_type" ("id" text not null, "label" text not null, "description" text null, "code" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "shipping_option_type_pkey" primary key ("id"));'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_shipping_option_type_deleted_at" ON "shipping_option_type" (deleted_at) WHERE deleted_at IS NOT NULL;'
)
this.addSql(`
alter table shipping_profile
alter column id type text using id::text;
alter table shipping_profile
alter column name type text using name::text;
alter table shipping_profile
alter column type type text using type::text;
create unique index if not exists "IDX_shipping_profile_name_unique"
on shipping_profile (name)
where (deleted_at IS NULL);
create index if not exists "IDX_shipping_profile_deleted_at"
on shipping_profile (deleted_at)
where (deleted_at IS NOT NULL);
`)
this.addSql(`
alter table shipping_option
alter column id type text using id::text;
alter table shipping_option
alter column name type text using name::text;
alter table shipping_option
add service_zone_id text not null;
alter table shipping_option
RENAME profile_id TO shipping_profile_id;
alter table shipping_option
alter column region_id drop not null;
alter table shipping_option
alter column price_type type text using price_type::text;
alter table shipping_option
alter column price_type set default 'calculated'::text;
alter table shipping_option
alter column data drop not null;
alter table shipping_option
add shipping_option_type_id text not null;
drop index if exists "IDX_5c58105f1752fca0f4ce69f466";
drop index if exists "IDX_c951439af4c98bf2bd7fb8726c";
drop index if exists "IDX_a0e206bfaed3cb63c186091734";
create index if not exists "IDX_shipping_option_service_zone_id"
on shipping_option (service_zone_id)
where (deleted_at IS NULL);
create index if not exists "IDX_shipping_option_shipping_profile_id"
on shipping_option (shipping_profile_id)
where (deleted_at IS NULL);
create index if not exists "IDX_shipping_option_provider_id"
on shipping_option (provider_id)
where (deleted_at IS NULL);
create index if not exists "IDX_shipping_option_shipping_option_type_id"
on shipping_option (shipping_option_type_id)
where (deleted_at IS NULL);
create index if not exists "IDX_shipping_option_deleted_at"
on shipping_option (deleted_at)
where (deleted_at IS NOT NULL);
alter table shipping_option
add constraint shipping_option_shipping_option_type_id_unique
unique (shipping_option_type_id);
alter table shipping_option
drop constraint "FK_5c58105f1752fca0f4ce69f4663";
alter table shipping_option
drop constraint "FK_c951439af4c98bf2bd7fb8726cd";
alter table shipping_option
drop constraint "FK_a0e206bfaed3cb63c1860917347";
alter table shipping_option
add constraint shipping_option_service_zone_id_foreign
foreign key (service_zone_id) references service_zone
on update cascade on delete cascade;
alter table shipping_option
add constraint shipping_option_shipping_profile_id_foreign
foreign key (shipping_profile_id) references shipping_profile
on update cascade on delete set null;
alter table shipping_option
add constraint shipping_option_provider_id_foreign
foreign key (provider_id) references fulfillment_provider
on update cascade on delete set null;
alter table shipping_option
add constraint shipping_option_shipping_option_type_id_foreign
foreign key (shipping_option_type_id) references shipping_option_type
on update cascade on delete cascade;
alter table shipping_option
drop constraint "CHK_7a367f5901ae0a5b0df75aee38";
alter table shipping_option
add constraint shipping_option_price_type_check
check (price_type = ANY (ARRAY ['calculated'::text, 'flat'::text]));
`)
this.addSql(
'create table if not exists "shipping_option_rule" ("id" text not null, "attribute" text not null, "operator" text check ("operator" in (\'in\', \'eq\', \'ne\', \'gt\', \'gte\', \'lt\', \'lte\', \'nin\')) not null, "value" jsonb null, "shipping_option_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "shipping_option_rule_pkey" primary key ("id"));'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_shipping_option_rule_shipping_option_id" ON "shipping_option_rule" (shipping_option_id) WHERE deleted_at IS NULL;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_shipping_option_rule_deleted_at" ON "shipping_option_rule" (deleted_at) WHERE deleted_at IS NOT NULL;'
)
this.addSql(`
alter table fulfillment
alter
column id type text using id::text;
alter table fulfillment
add packed_at timestamp with time zone;
alter table fulfillment
alter column data drop not null;
alter table fulfillment
add delivered_at timestamp with time zone;
alter table fulfillment
add shipping_option_id text;
alter table fulfillment
add delivery_address_id text;
alter table fulfillment
alter
column provider_id type text using provider_id::text;
alter table fulfillment
add deleted_at timestamp with time zone;
alter table fulfillment
alter
column location_id type text using location_id::text;
alter table fulfillment
alter column location_id set not null;
drop index if exists "IDX_d73e55964e0ff2db8f03807d52";
drop index if exists "IDX_a52e234f729db789cf473297a5";
drop index if exists "IDX_f129acc85e346a10eed12b86fc";
drop index if exists "IDX_beb35a6de60a6c4f91d5ae57e4";
create index if not exists "IDX_fulfillment_location_id"
on fulfillment (location_id) where (deleted_at IS NULL);
create index if not exists"IDX_fulfillment_provider_id"
on fulfillment (provider_id) where (deleted_at IS NULL);
create index if not exists"IDX_fulfillment_shipping_option_id"
on fulfillment (shipping_option_id) where (deleted_at IS NULL);
create index if not exists"IDX_fulfillment_deleted_at"
on fulfillment (deleted_at) where (deleted_at IS NOT NULL);
alter table fulfillment
add constraint fulfillment_delivery_address_id_unique
unique (delivery_address_id);
alter table fulfillment
drop
constraint "FK_a52e234f729db789cf473297a5c"; /* swap id */
alter table fulfillment
drop
constraint "FK_f129acc85e346a10eed12b86fca"; /* order id */
alter table fulfillment
drop
constraint "FK_beb35a6de60a6c4f91d5ae57e44"; /* provider id */
alter table fulfillment
drop
constraint "FK_d73e55964e0ff2db8f03807d52e"; /* claim id */
alter table fulfillment
add constraint fulfillment_provider_id_foreign
foreign key (provider_id) references fulfillment_provider
on update cascade on delete set null;
alter table fulfillment
add constraint fulfillment_shipping_option_id_foreign
foreign key (shipping_option_id) references shipping_option
on update cascade on delete set null;
alter table fulfillment
add constraint fulfillment_delivery_address_id_foreign
foreign key (delivery_address_id) references fulfillment_address
on update cascade on delete cascade;
`)
this.addSql(
'create table if not exists "fulfillment_label" ("id" text not null, "tracking_number" text not null, "tracking_url" text not null, "label_url" text not null, "fulfillment_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_label_pkey" primary key ("id"));'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_label_fulfillment_id" ON "fulfillment_label" (fulfillment_id) WHERE deleted_at IS NULL;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_label_deleted_at" ON "fulfillment_label" (deleted_at) WHERE deleted_at IS NOT NULL;'
)
this.addSql(`
alter table fulfillment_item
add id text not null;
alter table fulfillment_item
add title text not null;
alter table fulfillment_item
add sku text not null;
alter table fulfillment_item
add barcode text not null;
alter table fulfillment_item
alter column quantity type numeric using quantity::numeric;
alter table fulfillment_item
add raw_quantity jsonb not null;
alter table fulfillment_item
add line_item_id text;
alter table fulfillment_item
add inventory_item_id text;
alter table fulfillment_item
alter column fulfillment_id type text using fulfillment_id::text;
alter table fulfillment_item
add created_at timestamp with time zone default now() not null;
alter table fulfillment_item
add updated_at timestamp with time zone default now() not null;
alter table fulfillment_item
add deleted_at timestamp with time zone;
create index if not exists "IDX_fulfillment_item_line_item_id"
on fulfillment_item (line_item_id)
where (deleted_at IS NULL);
create index if not exists "IDX_fulfillment_item_inventory_item_id"
on fulfillment_item (inventory_item_id)
where (deleted_at IS NULL);
create index if not exists "IDX_fulfillment_item_fulfillment_id"
on fulfillment_item (fulfillment_id)
where (deleted_at IS NULL);
create index if not exists "IDX_fulfillment_item_deleted_at"
on fulfillment_item (deleted_at)
where (deleted_at IS NOT NULL);
alter table fulfillment_item
drop constraint "FK_a033f83cc6bd7701a5687ab4b38"; /* fulfillment id */
alter table fulfillment_item
drop constraint "FK_e13ff60e74206b747a1896212d1"; /* item id */
alter table fulfillment_item
drop constraint "PK_bc3e8a388de75db146a249922e0"; /* item id + fulfillment id PK */
alter table fulfillment_item
alter column item_id drop not null;
alter table fulfillment_item
add constraint fulfillment_item_fulfillment_id_foreign
foreign key (fulfillment_id) references fulfillment
on update cascade on delete cascade;
alter table "fulfillment_item" add primary key ("id");
`)
this.addSql(
'alter table if exists "service_zone" add constraint "service_zone_fulfillment_set_id_foreign" foreign key ("fulfillment_set_id") references "fulfillment_set" ("id") on update cascade on delete cascade;'
)
this.addSql(
'alter table if exists "geo_zone" add constraint "geo_zone_service_zone_id_foreign" foreign key ("service_zone_id") references "service_zone" ("id") on update cascade on delete cascade;'
)
this.addSql(
'alter table if exists "shipping_option_rule" add constraint "shipping_option_rule_shipping_option_id_foreign" foreign key ("shipping_option_id") references "shipping_option" ("id") on update cascade on delete cascade;'
)
this.addSql(
'alter table if exists "fulfillment_label" add constraint "fulfillment_label_fulfillment_id_foreign" foreign key ("fulfillment_id") references "fulfillment" ("id") on update cascade on delete cascade;'
)
}
/**
* This migration is for be initial setup of the fulfillment module in the case of
* a new database. It will create all the necessary tables and indexes.
*/
async function migrateUpModuleMigration(
this: Migration20240311145700_InitialSetupMigration
) {
this.addSql(
'create table if not exists "fulfillment_address" ("id" text not null, "company" text null, "first_name" text null, "last_name" text null, "address_1" text null, "address_2" text null, "city" text null, "country_code" text null, "province" text null, "postal_code" text null, "phone" text null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_address_pkey" primary key ("id"));'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_address_deleted_at" ON "fulfillment_address" (deleted_at) WHERE deleted_at IS NOT NULL;'
)
this.addSql(
'create table if not exists "fulfillment_provider" ("id" text not null, "is_enabled" boolean not null default true, constraint "fulfillment_provider_pkey" primary key ("id"));'
)
this.addSql(
'create table if not exists "fulfillment_set" ("id" text not null, "name" text not null, "type" text not null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_set_pkey" primary key ("id"));'
)
this.addSql(
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_fulfillment_set_name_unique" ON "fulfillment_set" (name) WHERE deleted_at IS NULL;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_set_deleted_at" ON "fulfillment_set" (deleted_at) WHERE deleted_at IS NOT NULL;'
)
this.addSql(
'create table if not exists "service_zone" ("id" text not null, "name" text not null, "metadata" jsonb null, "fulfillment_set_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "service_zone_pkey" primary key ("id"));'
)
this.addSql(
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_service_zone_name_unique" ON "service_zone" (name) WHERE deleted_at IS NULL;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_service_zone_fulfillment_set_id" ON "service_zone" (fulfillment_set_id) WHERE deleted_at IS NULL;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_service_zone_deleted_at" ON "service_zone" (deleted_at) WHERE deleted_at IS NOT NULL;'
)
this.addSql(
'create table if not exists "geo_zone" ("id" text not null, "type" text check ("type" in (\'country\', \'province\', \'city\', \'zip\')) not null default \'country\', "country_code" text not null, "province_code" text null, "city" text null, "service_zone_id" text not null, "postal_expression" jsonb null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "geo_zone_pkey" primary key ("id"));'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_geo_zone_country_code" ON "geo_zone" (country_code) WHERE deleted_at IS NULL;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_geo_zone_province_code" ON "geo_zone" (province_code) WHERE deleted_at IS NULL AND province_code IS NOT NULL;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_geo_zone_city" ON "geo_zone" (city) WHERE deleted_at IS NULL AND city IS NOT NULL;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_geo_zone_service_zone_id" ON "geo_zone" (service_zone_id) WHERE deleted_at IS NULL;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_geo_zone_deleted_at" ON "geo_zone" (deleted_at) WHERE deleted_at IS NOT NULL;'
)
this.addSql(
'create table if not exists "shipping_option_type" ("id" text not null, "label" text not null, "description" text null, "code" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "shipping_option_type_pkey" primary key ("id"));'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_shipping_option_type_deleted_at" ON "shipping_option_type" (deleted_at) WHERE deleted_at IS NOT NULL;'
)
this.addSql(
'create table if not exists "shipping_profile" ("id" text not null, "name" text not null, "type" text not null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "shipping_profile_pkey" primary key ("id"));'
)
this.addSql(
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_shipping_profile_name_unique" ON "shipping_profile" (name) WHERE deleted_at IS NULL;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_shipping_profile_deleted_at" ON "shipping_profile" (deleted_at) WHERE deleted_at IS NOT NULL;'
)
this.addSql(
'create table if not exists "shipping_option" ("id" text not null, "name" text not null, "price_type" text check ("price_type" in (\'calculated\', \'flat\')) not null default \'flat\', "service_zone_id" text not null, "shipping_profile_id" text null, "provider_id" text null, "data" jsonb null, "metadata" jsonb null, "shipping_option_type_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "shipping_option_pkey" primary key ("id"));'
)
this.addSql(
'alter table if exists "shipping_option" add constraint "shipping_option_shipping_option_type_id_unique" unique ("shipping_option_type_id");'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_shipping_option_service_zone_id" ON "shipping_option" (service_zone_id) WHERE deleted_at IS NULL;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_shipping_option_shipping_profile_id" ON "shipping_option" (shipping_profile_id) WHERE deleted_at IS NULL;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_shipping_option_provider_id" ON "shipping_option" (provider_id) WHERE deleted_at IS NULL;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_shipping_option_shipping_option_type_id" ON "shipping_option" (shipping_option_type_id) WHERE deleted_at IS NULL;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_shipping_option_deleted_at" ON "shipping_option" (deleted_at) WHERE deleted_at IS NOT NULL;'
)
this.addSql(
'create table if not exists "shipping_option_rule" ("id" text not null, "attribute" text not null, "operator" text check ("operator" in (\'in\', \'eq\', \'ne\', \'gt\', \'gte\', \'lt\', \'lte\', \'nin\')) not null, "value" jsonb null, "shipping_option_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "shipping_option_rule_pkey" primary key ("id"));'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_shipping_option_rule_shipping_option_id" ON "shipping_option_rule" (shipping_option_id) WHERE deleted_at IS NULL;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_shipping_option_rule_deleted_at" ON "shipping_option_rule" (deleted_at) WHERE deleted_at IS NOT NULL;'
)
this.addSql(
'create table if not exists "fulfillment" ("id" text not null, "location_id" text not null, "packed_at" timestamptz null, "shipped_at" timestamptz null, "delivered_at" timestamptz null, "canceled_at" timestamptz null, "data" jsonb null, "provider_id" text null, "shipping_option_id" text null, "metadata" jsonb null, "delivery_address_id" text null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_pkey" primary key ("id"));'
)
this.addSql(
'alter table if exists "fulfillment" add constraint "fulfillment_delivery_address_id_unique" unique ("delivery_address_id");'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_location_id" ON "fulfillment" (location_id) WHERE deleted_at IS NULL;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_provider_id" ON "fulfillment" (provider_id) WHERE deleted_at IS NULL;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_shipping_option_id" ON "fulfillment" (shipping_option_id) WHERE deleted_at IS NULL;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_deleted_at" ON "fulfillment" (deleted_at) WHERE deleted_at IS NOT NULL;'
)
this.addSql(
'create table if not exists "fulfillment_label" ("id" text not null, "tracking_number" text not null, "tracking_url" text not null, "label_url" text not null, "fulfillment_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_label_pkey" primary key ("id"));'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_label_fulfillment_id" ON "fulfillment_label" (fulfillment_id) WHERE deleted_at IS NULL;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_label_deleted_at" ON "fulfillment_label" (deleted_at) WHERE deleted_at IS NOT NULL;'
)
this.addSql(
'create table if not exists "fulfillment_item" ("id" text not null, "title" text not null, "sku" text not null, "barcode" text not null, "quantity" numeric not null, "raw_quantity" jsonb not null, "line_item_id" text null, "inventory_item_id" text null, "fulfillment_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_item_pkey" primary key ("id"));'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_item_line_item_id" ON "fulfillment_item" (line_item_id) WHERE deleted_at IS NULL;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_item_inventory_item_id" ON "fulfillment_item" (inventory_item_id) WHERE deleted_at IS NULL;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_item_fulfillment_id" ON "fulfillment_item" (fulfillment_id) WHERE deleted_at IS NULL;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_fulfillment_item_deleted_at" ON "fulfillment_item" (deleted_at) WHERE deleted_at IS NOT NULL;'
)
this.addSql(
'alter table if exists "service_zone" add constraint "service_zone_fulfillment_set_id_foreign" foreign key ("fulfillment_set_id") references "fulfillment_set" ("id") on update cascade on delete cascade;'
)
this.addSql(
'alter table if exists "geo_zone" add constraint "geo_zone_service_zone_id_foreign" foreign key ("service_zone_id") references "service_zone" ("id") on update cascade on delete cascade;'
)
this.addSql(
'alter table if exists "shipping_option" add constraint "shipping_option_service_zone_id_foreign" foreign key ("service_zone_id") references "service_zone" ("id") on update cascade on delete cascade;'
)
this.addSql(
'alter table if exists "shipping_option" add constraint "shipping_option_shipping_profile_id_foreign" foreign key ("shipping_profile_id") references "shipping_profile" ("id") on update cascade on delete set null;'
)
this.addSql(
'alter table if exists "shipping_option" add constraint "shipping_option_provider_id_foreign" foreign key ("provider_id") references "fulfillment_provider" ("id") on update cascade on delete set null;'
)
this.addSql(
'alter table if exists "shipping_option" add constraint "shipping_option_shipping_option_type_id_foreign" foreign key ("shipping_option_type_id") references "shipping_option_type" ("id") on update cascade on delete cascade;'
)
this.addSql(
'alter table if exists "shipping_option_rule" add constraint "shipping_option_rule_shipping_option_id_foreign" foreign key ("shipping_option_id") references "shipping_option" ("id") on update cascade on delete cascade;'
)
this.addSql(
'alter table if exists "fulfillment" add constraint "fulfillment_provider_id_foreign" foreign key ("provider_id") references "fulfillment_provider" ("id") on update cascade on delete set null;'
)
this.addSql(
'alter table if exists "fulfillment" add constraint "fulfillment_shipping_option_id_foreign" foreign key ("shipping_option_id") references "shipping_option" ("id") on update cascade on delete set null;'
)
this.addSql(
'alter table if exists "fulfillment" add constraint "fulfillment_delivery_address_id_foreign" foreign key ("delivery_address_id") references "fulfillment_address" ("id") on update cascade on delete cascade;'
)
this.addSql(
'alter table if exists "fulfillment_label" add constraint "fulfillment_label_fulfillment_id_foreign" foreign key ("fulfillment_id") references "fulfillment" ("id") on update cascade on delete cascade;'
)
this.addSql(
'alter table if exists "fulfillment_item" add constraint "fulfillment_item_fulfillment_id_foreign" foreign key ("fulfillment_id") references "fulfillment" ("id") on update cascade on delete cascade;'
)
}

View File

@@ -0,0 +1,91 @@
import { DAL } from "@medusajs/types"
import {
createPsqlIndexStatementHelper,
generateEntityId,
} from "@medusajs/utils"
import {
BeforeCreate,
Entity,
OnInit,
OptionalProps,
PrimaryKey,
Property,
} from "@mikro-orm/core"
type OptionalAddressProps = DAL.SoftDeletableEntityDateColumns
const FulfillmentDeletedAtIndex = createPsqlIndexStatementHelper({
tableName: "fulfillment_address",
columns: "deleted_at",
where: "deleted_at IS NOT NULL",
})
@Entity({ tableName: "fulfillment_address" })
export default class Address {
[OptionalProps]: OptionalAddressProps
@PrimaryKey({ columnType: "text" })
id!: string
@Property({ columnType: "text", nullable: true })
company: string | null = null
@Property({ columnType: "text", nullable: true })
first_name: string | null = null
@Property({ columnType: "text", nullable: true })
last_name: string | null = null
@Property({ columnType: "text", nullable: true })
address_1: string | null = null
@Property({ columnType: "text", nullable: true })
address_2: string | null = null
@Property({ columnType: "text", nullable: true })
city: string | null = null
@Property({ columnType: "text", nullable: true })
country_code: string | null = null
@Property({ columnType: "text", nullable: true })
province: string | null = null
@Property({ columnType: "text", nullable: true })
postal_code: string | null = null
@Property({ columnType: "text", nullable: true })
phone: string | null = null
@Property({ columnType: "jsonb", nullable: true })
metadata: Record<string, unknown> | null = null
@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
@Property({ columnType: "timestamptz", nullable: true })
@FulfillmentDeletedAtIndex.MikroORMIndex()
deleted_at: Date | null = null
@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "fuladdr")
}
@OnInit()
onInit() {
this.id = generateEntityId(this.id, "fuladdr")
}
}

View File

@@ -0,0 +1,121 @@
import {
BigNumber,
createPsqlIndexStatementHelper,
DALUtils,
generateEntityId,
MikroOrmBigNumberProperty,
} from "@medusajs/utils"
import { BigNumberRawValue, DAL } from "@medusajs/types"
import {
BeforeCreate,
Entity,
Filter,
ManyToOne,
OnInit,
OptionalProps,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import Fulfillment from "./fulfillment"
type FulfillmentItemOptionalProps = DAL.SoftDeletableEntityDateColumns
const FulfillmentIdIndex = createPsqlIndexStatementHelper({
tableName: "fulfillment_item",
columns: "fulfillment_id",
where: "deleted_at IS NULL",
})
const LineItemIdIndex = createPsqlIndexStatementHelper({
tableName: "fulfillment_item",
columns: "line_item_id",
where: "deleted_at IS NULL",
})
const InventoryItemIdIndex = createPsqlIndexStatementHelper({
tableName: "fulfillment_item",
columns: "inventory_item_id",
where: "deleted_at IS NULL",
})
const FulfillmentItemDeletedAtIndex = createPsqlIndexStatementHelper({
tableName: "fulfillment_item",
columns: "deleted_at",
where: "deleted_at IS NOT NULL",
})
@Entity()
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
export default class FulfillmentItem {
[OptionalProps]?: FulfillmentItemOptionalProps
@PrimaryKey({ columnType: "text" })
id: string
@Property({ columnType: "text" })
title: string
@Property({ columnType: "text" })
sku: string
@Property({ columnType: "text" })
barcode: string
@MikroOrmBigNumberProperty()
quantity: BigNumber | number
@Property({ columnType: "jsonb" })
raw_quantity: BigNumberRawValue
@Property({ columnType: "text", nullable: true })
@LineItemIdIndex.MikroORMIndex()
line_item_id: string | null = null
@Property({ columnType: "text", nullable: true })
@InventoryItemIdIndex.MikroORMIndex()
inventory_item_id: string | null = null
@ManyToOne(() => Fulfillment, {
columnType: "text",
mapToPk: true,
fieldName: "fulfillment_id",
onDelete: "cascade",
})
@FulfillmentIdIndex.MikroORMIndex()
fulfillment_id: string
@ManyToOne(() => Fulfillment, { persist: false })
fulfillment: Fulfillment
@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
@FulfillmentItemDeletedAtIndex.MikroORMIndex()
@Property({ columnType: "timestamptz", nullable: true })
deleted_at: Date | null = null
@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "fulit")
this.fulfillment_id ??= this.fulfillment.id
}
@OnInit()
onInit() {
this.id = generateEntityId(this.id, "fulit")
this.fulfillment_id ??= this.fulfillment.id
}
}

View File

@@ -0,0 +1,93 @@
import {
createPsqlIndexStatementHelper,
DALUtils,
generateEntityId,
} from "@medusajs/utils"
import { DAL } from "@medusajs/types"
import {
BeforeCreate,
Entity,
Filter,
ManyToOne,
OnInit,
OptionalProps,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import Fulfillment from "./fulfillment"
type FulfillmentLabelOptionalProps = DAL.SoftDeletableEntityDateColumns
const FulfillmentIdIndex = createPsqlIndexStatementHelper({
tableName: "fulfillment_label",
columns: "fulfillment_id",
where: "deleted_at IS NULL",
})
const DeletedAtIndex = createPsqlIndexStatementHelper({
tableName: "fulfillment_label",
columns: "deleted_at",
where: "deleted_at IS NOT NULL",
})
@Entity()
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
export default class FulfillmentLabel {
[OptionalProps]?: FulfillmentLabelOptionalProps
@PrimaryKey({ columnType: "text" })
id: string
@Property({ columnType: "text" })
tracking_number: string
@Property({ columnType: "text" })
tracking_url: string
@Property({ columnType: "text" })
label_url: string
@ManyToOne(() => Fulfillment, {
columnType: "text",
mapToPk: true,
fieldName: "fulfillment_id",
onDelete: "cascade",
})
@FulfillmentIdIndex.MikroORMIndex()
fulfillment_id: string
@ManyToOne(() => Fulfillment, { persist: false })
fulfillment: Fulfillment
@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
@Property({ columnType: "timestamptz", nullable: true })
@DeletedAtIndex.MikroORMIndex()
deleted_at: Date | null = null
@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "fulla")
this.fulfillment_id ??= this.fulfillment.id
}
@OnInit()
onInit() {
this.id = generateEntityId(this.id, "fulla")
this.fulfillment_id ??= this.fulfillment.id
}
}

View File

@@ -0,0 +1,27 @@
import { generateEntityId } from "@medusajs/utils"
import {
BeforeCreate,
Entity,
OnInit,
PrimaryKey,
Property,
} from "@mikro-orm/core"
@Entity()
export default class FulfillmentProvider {
@PrimaryKey({ columnType: "text" })
id: string
@Property({ columnType: "boolean", defaultRaw: "true" })
is_enabled: boolean = true
@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "serpro")
}
@OnInit()
onInit() {
this.id = generateEntityId(this.id, "serpro")
}
}

View File

@@ -0,0 +1,89 @@
import {
createPsqlIndexStatementHelper,
DALUtils,
generateEntityId,
} from "@medusajs/utils"
import { DAL } from "@medusajs/types"
import {
BeforeCreate,
Cascade,
Collection,
Entity,
Filter,
OneToMany,
OnInit,
OptionalProps,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import ServiceZone from "./service-zone"
type FulfillmentSetOptionalProps = DAL.SoftDeletableEntityDateColumns
const DeletedAtIndex = createPsqlIndexStatementHelper({
tableName: "fulfillment_set",
columns: "deleted_at",
where: "deleted_at IS NOT NULL",
})
const NameIndex = createPsqlIndexStatementHelper({
tableName: "fulfillment_set",
columns: "name",
unique: true,
where: "deleted_at IS NULL",
})
@Entity()
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
export default class FulfillmentSet {
[OptionalProps]?: FulfillmentSetOptionalProps
@PrimaryKey({ columnType: "text" })
id: string
@Property({ columnType: "text" })
@NameIndex.MikroORMIndex()
name: string
@Property({ columnType: "text" })
type: string
@Property({ columnType: "jsonb", nullable: true })
metadata: Record<string, unknown> | null = null
@OneToMany(() => ServiceZone, "fulfillment_set", {
cascade: [Cascade.PERSIST, "soft-remove"] as any,
orphanRemoval: true,
})
service_zones = new Collection<ServiceZone>(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
@Property({ columnType: "timestamptz", nullable: true })
@DeletedAtIndex.MikroORMIndex()
deleted_at: Date | null = null
@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "fuset")
}
@OnInit()
onInit() {
this.id = generateEntityId(this.id, "fuset")
}
}

View File

@@ -0,0 +1,173 @@
import {
createPsqlIndexStatementHelper,
DALUtils,
generateEntityId,
} from "@medusajs/utils"
import { DAL } from "@medusajs/types"
import {
BeforeCreate,
Cascade,
Collection,
Entity,
Filter,
ManyToOne,
OneToMany,
OneToOne,
OnInit,
OptionalProps,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import Address from "./address"
import FulfillmentItem from "./fulfillment-item"
import FulfillmentLabel from "./fulfillment-label"
import FulfillmentProvider from "./fulfillment-provider"
import ShippingOption from "./shipping-option"
type FulfillmentOptionalProps = DAL.SoftDeletableEntityDateColumns
const FulfillmentDeletedAtIndex = createPsqlIndexStatementHelper({
tableName: "fulfillment",
columns: "deleted_at",
where: "deleted_at IS NOT NULL",
})
const FulfillmentProviderIdIndex = createPsqlIndexStatementHelper({
tableName: "fulfillment",
columns: "provider_id",
where: "deleted_at IS NULL",
})
const FulfillmentLocationIdIndex = createPsqlIndexStatementHelper({
tableName: "fulfillment",
columns: "location_id",
where: "deleted_at IS NULL",
})
const FulfillmentShippingOptionIdIndex = createPsqlIndexStatementHelper({
tableName: "fulfillment",
columns: "shipping_option_id",
where: "deleted_at IS NULL",
})
@Entity()
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
export default class Fulfillment {
[OptionalProps]?: FulfillmentOptionalProps
@PrimaryKey({ columnType: "text" })
id: string
@Property({ columnType: "text" })
@FulfillmentLocationIdIndex.MikroORMIndex()
location_id: string
@Property({
columnType: "timestamptz",
nullable: true,
})
packed_at: Date | null = null
@Property({
columnType: "timestamptz",
nullable: true,
})
shipped_at: Date | null = null
@Property({
columnType: "timestamptz",
nullable: true,
})
delivered_at: Date | null = null
@Property({
columnType: "timestamptz",
nullable: true,
})
canceled_at: Date | null = null
@Property({ columnType: "jsonb", nullable: true })
data: Record<string, unknown> | null = null
@ManyToOne(() => FulfillmentProvider, {
columnType: "text",
fieldName: "provider_id",
mapToPk: true,
nullable: true,
onDelete: "set null",
})
@FulfillmentProviderIdIndex.MikroORMIndex()
provider_id: string
@ManyToOne(() => ShippingOption, {
columnType: "text",
fieldName: "shipping_option_id",
nullable: true,
mapToPk: true,
onDelete: "set null",
})
@FulfillmentShippingOptionIdIndex.MikroORMIndex()
shipping_option_id: string | null = null
@Property({ columnType: "jsonb", nullable: true })
metadata: Record<string, unknown> | null = null
@ManyToOne(() => ShippingOption, { persist: false })
shipping_option: ShippingOption | null
@ManyToOne(() => FulfillmentProvider, { persist: false })
provider: FulfillmentProvider
@OneToOne({
entity: () => Address,
owner: true,
cascade: [Cascade.PERSIST, "soft-remove"] as any,
nullable: true,
onDelete: "cascade",
})
delivery_address!: Address
@OneToMany(() => FulfillmentItem, (item) => item.fulfillment, {
cascade: [Cascade.PERSIST, "soft-remove"] as any,
orphanRemoval: true,
})
items = new Collection<FulfillmentItem>(this)
@OneToMany(() => FulfillmentLabel, (label) => label.fulfillment, {
cascade: [Cascade.PERSIST, "soft-remove"] as any,
orphanRemoval: true,
})
labels = new Collection<FulfillmentLabel>(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
@FulfillmentDeletedAtIndex.MikroORMIndex()
@Property({ columnType: "timestamptz", nullable: true })
deleted_at: Date | null = null
@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "ful")
this.provider_id ??= this.provider_id ?? this.provider?.id
}
@OnInit()
onInit() {
this.id = generateEntityId(this.id, "ful")
this.provider_id ??= this.provider_id ?? this.provider?.id
}
}

View File

@@ -0,0 +1,127 @@
import {
createPsqlIndexStatementHelper,
DALUtils,
generateEntityId,
GeoZoneType,
} from "@medusajs/utils"
import { DAL } from "@medusajs/types"
import {
BeforeCreate,
Entity,
Enum,
Filter,
ManyToOne,
OnInit,
OptionalProps,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import ServiceZone from "./service-zone"
type GeoZoneOptionalProps = DAL.SoftDeletableEntityDateColumns
const DeletedAtIndex = createPsqlIndexStatementHelper({
tableName: "geo_zone",
columns: "deleted_at",
where: "deleted_at IS NOT NULL",
})
const CountryCodeIndex = createPsqlIndexStatementHelper({
tableName: "geo_zone",
columns: "country_code",
where: "deleted_at IS NULL",
})
const ProvinceCodeIndex = createPsqlIndexStatementHelper({
tableName: "geo_zone",
columns: "province_code",
where: "deleted_at IS NULL AND province_code IS NOT NULL",
})
const CityIndex = createPsqlIndexStatementHelper({
tableName: "geo_zone",
columns: "city",
where: "deleted_at IS NULL AND city IS NOT NULL",
})
const ServiceZoneIdIndex = createPsqlIndexStatementHelper({
tableName: "geo_zone",
columns: "service_zone_id",
where: "deleted_at IS NULL",
})
@Entity()
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
export default class GeoZone {
[OptionalProps]?: GeoZoneOptionalProps
@PrimaryKey({ columnType: "text" })
id: string
@Enum({ items: () => GeoZoneType, default: GeoZoneType.COUNTRY })
type: GeoZoneType
@CountryCodeIndex.MikroORMIndex()
@Property({ columnType: "text" })
country_code: string
@ProvinceCodeIndex.MikroORMIndex()
@Property({ columnType: "text", nullable: true })
province_code: string | null = null
@CityIndex.MikroORMIndex()
@Property({ columnType: "text", nullable: true })
city: string | null = null
@ManyToOne(() => ServiceZone, {
type: "text",
mapToPk: true,
fieldName: "service_zone_id",
onDelete: "cascade",
})
@ServiceZoneIdIndex.MikroORMIndex()
service_zone_id: string
@Property({ columnType: "jsonb", nullable: true })
postal_expression: Record<string, unknown> | null = null
@Property({ columnType: "jsonb", nullable: true })
metadata: Record<string, unknown> | null = null
@ManyToOne(() => ServiceZone, {
persist: false,
})
service_zone: ServiceZone
@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
@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
}
}

View File

@@ -0,0 +1,13 @@
export { default as Address } from "./address"
export { default as Fulfillment } from "./fulfillment"
export { default as FulfillmentItem } from "./fulfillment-item"
export { default as FulfillmentLabel } from "./fulfillment-label"
export { default as FulfillmentProvider } from "./fulfillment-provider"
export { default as FulfillmentSet } from "./fulfillment-set"
export { default as GeoZone } from "./geo-zone"
export { default as ServiceZone } from "./service-zone"
export { default as ShippingOption } from "./shipping-option"
export { default as ShippingOptionRule } from "./shipping-option-rule"
export { default as ShippingOptionType } from "./shipping-option-type"
export { default as ShippingProfile } from "./shipping-profile"

View File

@@ -0,0 +1,125 @@
import {
createPsqlIndexStatementHelper,
DALUtils,
generateEntityId,
} from "@medusajs/utils"
import { DAL } from "@medusajs/types"
import {
BeforeCreate,
Cascade,
Collection,
Entity,
Filter,
Index,
ManyToOne,
OneToMany,
OnInit,
OptionalProps,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import FulfillmentSet from "./fulfillment-set"
import GeoZone from "./geo-zone"
import ShippingOption from "./shipping-option"
type ServiceZoneOptionalProps = DAL.SoftDeletableEntityDateColumns
const deletedAtIndexName = "IDX_service_zone_deleted_at"
const deletedAtIndexStatement = createPsqlIndexStatementHelper({
name: deletedAtIndexName,
tableName: "service_zone",
columns: "deleted_at",
where: "deleted_at IS NOT NULL",
}).expression
const NameIndex = createPsqlIndexStatementHelper({
tableName: "service_zone",
columns: "name",
unique: true,
where: "deleted_at IS NULL",
})
const FulfillmentSetIdIndex = createPsqlIndexStatementHelper({
tableName: "service_zone",
columns: "fulfillment_set_id",
where: "deleted_at IS NULL",
})
@Entity()
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
export default class ServiceZone {
[OptionalProps]?: ServiceZoneOptionalProps
@PrimaryKey({ columnType: "text" })
id: string
@Property({ columnType: "text" })
@NameIndex.MikroORMIndex()
name: string
@Property({ columnType: "jsonb", nullable: true })
metadata: Record<string, unknown> | null = null
@ManyToOne(() => FulfillmentSet, {
type: "text",
mapToPk: true,
fieldName: "fulfillment_set_id",
onDelete: "cascade",
})
@FulfillmentSetIdIndex.MikroORMIndex()
fulfillment_set_id: string
@ManyToOne(() => FulfillmentSet, { persist: false })
fulfillment_set: FulfillmentSet
@OneToMany(() => GeoZone, "service_zone", {
cascade: [Cascade.PERSIST, "soft-remove"] as any,
orphanRemoval: true,
})
geo_zones = new Collection<GeoZone>(this)
@OneToMany(
() => ShippingOption,
(shippingOption) => shippingOption.service_zone,
{
cascade: [Cascade.PERSIST, "soft-remove"] as any,
orphanRemoval: true,
}
)
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
@Index({
name: deletedAtIndexName,
expression: deletedAtIndexStatement,
})
@Property({ columnType: "timestamptz", nullable: true })
deleted_at: Date | null = null
@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
}
}

View File

@@ -0,0 +1,99 @@
import { DAL } from "@medusajs/types"
import {
createPsqlIndexStatementHelper,
DALUtils,
generateEntityId,
RuleOperator,
} from "@medusajs/utils"
import {
BeforeCreate,
Entity,
Enum,
Filter,
ManyToOne,
OnInit,
OptionalProps,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import ShippingOption from "./shipping-option"
type ShippingOptionRuleOptionalProps = DAL.SoftDeletableEntityDateColumns
const DeletedAtIndex = createPsqlIndexStatementHelper({
tableName: "shipping_option_rule",
columns: "deleted_at",
where: "deleted_at IS NOT NULL",
})
const ShippingOptionIdIndex = createPsqlIndexStatementHelper({
tableName: "shipping_option_rule",
columns: "shipping_option_id",
where: "deleted_at IS NULL",
})
@Entity()
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
export default class ShippingOptionRule {
[OptionalProps]?: ShippingOptionRuleOptionalProps
@PrimaryKey({ columnType: "text" })
id: string
@Property({ columnType: "text" })
attribute: string
@Enum({
items: () => Object.values(RuleOperator),
columnType: "text",
})
operator: Lowercase<keyof typeof RuleOperator>
@Property({ columnType: "jsonb", nullable: true })
value: string | string[] | null = null
@ManyToOne(() => ShippingOption, {
type: "text",
mapToPk: true,
fieldName: "shipping_option_id",
onDelete: "cascade",
})
@ShippingOptionIdIndex.MikroORMIndex()
shipping_option_id: string
@ManyToOne(() => ShippingOption, {
persist: false,
})
shipping_option: ShippingOption
@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
@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "sorul")
this.shipping_option_id ??= this.shipping_option?.id
}
@OnInit()
onInit() {
this.id = generateEntityId(this.id, "sorul")
this.shipping_option_id ??= this.shipping_option?.id
}
}

View File

@@ -0,0 +1,85 @@
import {
createPsqlIndexStatementHelper,
DALUtils,
generateEntityId,
} from "@medusajs/utils"
import { DAL } from "@medusajs/types"
import {
BeforeCreate,
Entity,
Filter,
OneToOne,
OnInit,
OptionalProps,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import ShippingOption from "./shipping-option"
type ShippingOptionTypeOptionalProps = DAL.SoftDeletableEntityDateColumns
const DeletedAtIndex = createPsqlIndexStatementHelper({
tableName: "shipping_option_type",
columns: "deleted_at",
where: "deleted_at IS NOT NULL",
})
const ShippingOptionIdIndex = createPsqlIndexStatementHelper({
tableName: "shipping_option_type",
columns: "shipping_option_id",
where: "deleted_at IS NULL",
})
@Entity()
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
export default class ShippingOptionType {
[OptionalProps]?: ShippingOptionTypeOptionalProps
@PrimaryKey({ columnType: "text" })
id: string
@Property({ columnType: "text" })
label: string
@Property({ columnType: "text", nullable: true })
description: string | null = null
@Property({ columnType: "text" })
code: string
@OneToOne(() => ShippingOption, (so) => so.type, {
type: "text",
onDelete: "cascade",
})
shipping_option: ShippingOption
@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
@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "sotype")
}
@OnInit()
onInit() {
this.id = generateEntityId(this.id, "sotype")
}
}

View File

@@ -0,0 +1,179 @@
import {
createPsqlIndexStatementHelper,
DALUtils,
generateEntityId,
ShippingOptionPriceType,
} from "@medusajs/utils"
import { DAL } from "@medusajs/types"
import {
BeforeCreate,
Cascade,
Collection,
Entity,
Enum,
Filter,
ManyToOne,
OneToMany,
OneToOne,
OnInit,
OptionalProps,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import Fulfillment from "./fulfillment"
import FulfillmentProvider from "./fulfillment-provider"
import ServiceZone from "./service-zone"
import ShippingOptionRule from "./shipping-option-rule"
import ShippingOptionType from "./shipping-option-type"
import ShippingProfile from "./shipping-profile"
type ShippingOptionOptionalProps = DAL.SoftDeletableEntityDateColumns
const DeletedAtIndex = createPsqlIndexStatementHelper({
tableName: "shipping_option",
columns: "deleted_at",
where: "deleted_at IS NOT NULL",
})
const ServiceZoneIdIndex = createPsqlIndexStatementHelper({
tableName: "shipping_option",
columns: "service_zone_id",
where: "deleted_at IS NULL",
})
const ShippingProfileIdIndex = createPsqlIndexStatementHelper({
tableName: "shipping_option",
columns: "shipping_profile_id",
where: "deleted_at IS NULL",
})
const FulfillmentProviderIdIndex = createPsqlIndexStatementHelper({
tableName: "shipping_option",
columns: "provider_id",
where: "deleted_at IS NULL",
})
const ShippingOptionTypeIdIndex = createPsqlIndexStatementHelper({
tableName: "shipping_option",
columns: "shipping_option_type_id",
where: "deleted_at IS NULL",
})
@Entity()
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
export default class ShippingOption {
[OptionalProps]?: ShippingOptionOptionalProps
@PrimaryKey({ columnType: "text" })
id: string
@Property({ columnType: "text" })
name: string
@Enum({
items: () => ShippingOptionPriceType,
default: ShippingOptionPriceType.FLAT,
})
price_type: ShippingOptionPriceType
@ManyToOne(() => ServiceZone, {
type: "text",
fieldName: "service_zone_id",
mapToPk: true,
onDelete: "cascade",
})
@ServiceZoneIdIndex.MikroORMIndex()
service_zone_id: string
@ManyToOne(() => ShippingProfile, {
type: "text",
fieldName: "shipping_profile_id",
mapToPk: true,
nullable: true,
onDelete: "set null",
})
@ShippingProfileIdIndex.MikroORMIndex()
shipping_profile_id: string | null
@ManyToOne(() => FulfillmentProvider, {
type: "text",
fieldName: "provider_id",
mapToPk: true,
nullable: true,
})
@FulfillmentProviderIdIndex.MikroORMIndex()
provider_id: string
@Property({ columnType: "text", persist: false })
@ShippingOptionTypeIdIndex.MikroORMIndex()
shipping_option_type_id: string | null = null
@Property({ columnType: "jsonb", nullable: true })
data: Record<string, unknown> | null = null
@Property({ columnType: "jsonb", nullable: true })
metadata: Record<string, unknown> | null = null
@ManyToOne(() => ServiceZone, { persist: false })
service_zone: ServiceZone
@ManyToOne(() => ShippingProfile, {
persist: false,
})
shipping_profile: ShippingProfile | null
@ManyToOne(() => FulfillmentProvider, {
persist: false,
})
provider: FulfillmentProvider | null
@OneToOne(() => ShippingOptionType, (so) => so.shipping_option, {
owner: true,
cascade: [Cascade.PERSIST, "soft-remove"] as any,
orphanRemoval: true,
fieldName: "shipping_option_type_id",
onDelete: "cascade",
})
type: ShippingOptionType
@OneToMany(() => ShippingOptionRule, "shipping_option", {
cascade: [Cascade.PERSIST, "soft-remove"] as any,
orphanRemoval: true,
})
rules = new Collection<ShippingOptionRule>(this)
@OneToMany(() => Fulfillment, (fulfillment) => fulfillment.shipping_option)
fulfillments = new Collection<Fulfillment>(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
@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "so")
this.shipping_option_type_id ??= this.type?.id
}
@OnInit()
onInit() {
this.id = generateEntityId(this.id, "so")
this.shipping_option_type_id ??= this.type?.id
}
}

View File

@@ -0,0 +1,91 @@
import {
createPsqlIndexStatementHelper,
DALUtils,
generateEntityId,
Searchable,
} from "@medusajs/utils"
import { DAL } from "@medusajs/types"
import {
BeforeCreate,
Collection,
Entity,
Filter,
OneToMany,
OnInit,
OptionalProps,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import ShippingOption from "./shipping-option"
type ShippingProfileOptionalProps = DAL.SoftDeletableEntityDateColumns
const DeletedAtIndex = createPsqlIndexStatementHelper({
tableName: "shipping_profile",
columns: "deleted_at",
where: "deleted_at IS NOT NULL",
})
const ShippingProfileTypeIndex = createPsqlIndexStatementHelper({
tableName: "shipping_profile",
columns: "name",
unique: true,
where: "deleted_at IS NULL",
})
@Entity()
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
export default class ShippingProfile {
[OptionalProps]?: ShippingProfileOptionalProps
@PrimaryKey({ columnType: "text" })
id: string
@Searchable()
@Property({ columnType: "text" })
@ShippingProfileTypeIndex.MikroORMIndex()
name: string
@Searchable()
@Property({ columnType: "text" })
type: string
@OneToMany(
() => ShippingOption,
(shippingOption) => shippingOption.shipping_profile
)
shipping_options = new Collection<ShippingOption>(this)
@Property({ columnType: "jsonb", nullable: true })
metadata: Record<string, unknown> | null = null
@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
@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "sp")
}
@OnInit()
onInit() {
this.id = generateEntityId(this.id, "sp")
}
}

View File

@@ -0,0 +1,44 @@
import { ModuleExports } from "@medusajs/types"
import * as ModuleServices from "@services"
import { FulfillmentModuleService } from "@services"
import { Modules } from "@medusajs/modules-sdk"
import * as ModuleModels from "@models"
import { ModulesSdkUtils } from "@medusajs/utils"
import * as ModuleRepositories from "@repositories"
import loadProviders from "./loaders/providers"
const migrationScriptOptions = {
moduleName: Modules.FULFILLMENT,
models: ModuleModels,
pathToMigrations: __dirname + "/migrations",
}
const runMigrations = ModulesSdkUtils.buildMigrationScript(
migrationScriptOptions
)
const revertMigration = ModulesSdkUtils.buildRevertMigrationScript(
migrationScriptOptions
)
const containerLoader = ModulesSdkUtils.moduleContainerLoaderFactory({
moduleModels: ModuleModels,
moduleRepositories: ModuleRepositories,
moduleServices: ModuleServices,
})
const connectionLoader = ModulesSdkUtils.mikroOrmConnectionLoaderFactory({
moduleName: Modules.FULFILLMENT,
moduleModels: Object.values(ModuleModels),
migrationsPath: __dirname + "/migrations",
})
const service = FulfillmentModuleService
const loaders = [containerLoader, connectionLoader, loadProviders]
export const moduleDefinition: ModuleExports = {
service,
loaders,
revertMigration,
runMigrations,
}

View File

@@ -0,0 +1,59 @@
/*
import { Context, FulfillmentTypes } from "@medusajs/types"
import { DALUtils, promiseAll } from "@medusajs/utils"
import { FulfillmentSet, ServiceZone } from "@models"
import { SqlEntityManager } from "@mikro-orm/postgresql"
interface CreateFulfillmentSetDTO
extends FulfillmentTypes.CreateFulfillmentSetDTO {
service_zones: { id: string; name: string }[]
}
export class FulfillmentSetRepository extends DALUtils.mikroOrmBaseRepositoryFactory<FulfillmentSet>(
FulfillmentSet
) {
async update(
data: {
entity: FulfillmentSet
update: FulfillmentTypes.FulfillmentSetDTO
}[],
context?: Context
): Promise<FulfillmentSet[]> {
const manager = this.getActiveManager<SqlEntityManager>(context)
// 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
}
}
*/

View File

@@ -0,0 +1 @@
export { MikroOrmBaseRepository as BaseRepository } from "@medusajs/utils"

View File

@@ -0,0 +1,29 @@
#!/usr/bin/env node
import { ModulesSdkUtils } from "@medusajs/utils"
import { Modules } from "@medusajs/modules-sdk"
import * as Models from "@models"
import { EOL } from "os"
const args = process.argv
const path = args.pop() as string
export default (async () => {
const { config } = await import("dotenv")
config()
if (!path) {
throw new Error(
`filePath is required.${EOL}Example: medusa-fulfillment-seed <filePath>`
)
}
const run = ModulesSdkUtils.buildSeedScript({
moduleName: Modules.FULFILLMENT,
models: Models,
pathToMigrations: __dirname + "/../../migrations",
seedHandler: async ({ manager, data }) => {
// TODO: Add seed logic
},
})
await run({ path })
})()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,104 @@
import {
Constructor,
DAL,
FulfillmentTypes,
IFulfillmentProvider,
} from "@medusajs/types"
import { ModulesSdkUtils, promiseAll } from "@medusajs/utils"
import { MedusaError } from "medusa-core-utils"
import { FulfillmentProvider } from "@models"
type InjectedDependencies = {
fulfillmentProviderRepository: DAL.RepositoryService
[key: `fp_${string}`]: FulfillmentTypes.IFulfillmentProvider
}
// TODO rework DTO's
export default class FulfillmentProviderService extends ModulesSdkUtils.internalModuleServiceFactory<InjectedDependencies>(
FulfillmentProvider
) {
protected readonly fulfillmentProviderRepository_: DAL.RepositoryService
constructor(container: InjectedDependencies) {
super(container)
this.fulfillmentProviderRepository_ =
container.fulfillmentProviderRepository
}
static getRegistrationIdentifier(
providerClass: Constructor<IFulfillmentProvider>,
optionName?: string
) {
return `${(providerClass as any).identifier}_${optionName}`
}
protected retrieveProviderRegistration(
providerId: string
): FulfillmentTypes.IFulfillmentProvider {
try {
return this.__container__[`fp_${providerId}`]
} catch (err) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Could not find a fulfillment provider with id: ${providerId}`
)
}
}
async listFulfillmentOptions(providerIds: string[]): Promise<any[]> {
return await promiseAll(
providerIds.map(async (p) => {
const provider = this.retrieveProviderRegistration(p)
return {
provider_id: p,
options: (await provider.getFulfillmentOptions()) as Record<
string,
unknown
>[],
}
})
)
}
async getFulfillmentOptions(
providerId: string
): Promise<Record<string, unknown>[]> {
const provider = this.retrieveProviderRegistration(providerId)
return await provider.getFulfillmentOptions()
}
async validateFulfillmentData(
providerId: string,
optionData: Record<string, unknown>,
data: Record<string, unknown>,
context: Record<string, unknown>
) {
const provider = this.retrieveProviderRegistration(providerId)
return await provider.validateFulfillmentData(optionData, data, context)
}
async validateOption(providerId: string, data: Record<string, unknown>) {
const provider = this.retrieveProviderRegistration(providerId)
return await provider.validateOption(data)
}
async createFulfillment(
providerId: string,
data: object,
items: object[],
order: object,
fulfillment: Record<string, unknown>
): Promise<Record<string, unknown>> {
const provider = this.retrieveProviderRegistration(providerId)
return await provider.createFulfillment(data, items, order, fulfillment)
}
async cancelFulfillment(
providerId: string,
fulfillment: Record<string, unknown>
): Promise<any> {
const provider = this.retrieveProviderRegistration(providerId)
return await provider.cancelFulfillment(fulfillment)
}
}

View File

@@ -0,0 +1,2 @@
export { default as FulfillmentModuleService } from "./fulfillment-module-service"
export { default as FulfillmentProviderService } from "./fulfillment-provider"

View File

@@ -0,0 +1,33 @@
import {
IEventBusModuleService,
Logger,
ModuleProviderExports,
ModuleServiceInitializeOptions,
} from "@medusajs/types"
export type InitializeModuleInjectableDependencies = {
logger?: Logger
eventBusService?: IEventBusModuleService
}
export const FulfillmentIdentifiersRegistrationName =
"fulfillment_providers_identifier"
export type FulfillmentModuleOptions =
Partial<ModuleServiceInitializeOptions> & {
/**
* Providers to be registered
*/
providers?: {
/**
* The module provider to be registered
*/
resolve: string | ModuleProviderExports
options: {
/**
* key value pair of the provider name and the configuration to be passed to the provider constructor
*/
config: Record<string, unknown>
}
}[]
}

View File

@@ -0,0 +1,6 @@
import { FulfillmentTypes } from "@medusajs/types"
export type UpdateShippingOptionsInput = Required<
Pick<FulfillmentTypes.UpdateShippingOptionDTO, "id">
> &
FulfillmentTypes.UpdateShippingOptionDTO

View File

@@ -0,0 +1,249 @@
import { RuleOperator } from "@medusajs/utils"
import { isContextValid } from "../utils"
describe("isContextValidForRules", () => {
const context = {
attribute1: "value1",
attribute2: "value2",
attribute3: "value3",
}
const validRule = {
attribute: "attribute1",
operator: RuleOperator.EQ,
value: "value1",
}
const invalidRule = {
attribute: "attribute2",
operator: RuleOperator.EQ,
value: "wrongValue",
}
it("returns true when all rules are valid", () => {
const rules = [validRule, validRule]
expect(isContextValid(context, rules)).toBe(true)
})
it("returns true when some rules are valid", () => {
const rules = [validRule, validRule]
const options = { someAreValid: true }
expect(isContextValid(context, rules, options)).toBe(true)
})
it("returns true when some rules are valid and someAreValid is true", () => {
const rules = [validRule, invalidRule]
const options = { someAreValid: true }
expect(isContextValid(context, rules, options)).toBe(true)
})
it("returns false when some rules are valid", () => {
const rules = [validRule, invalidRule]
expect(isContextValid(context, rules)).toBe(false)
})
it("returns false when no rules are valid and someAreValid is true", () => {
const rules = [invalidRule, invalidRule]
const options = { someAreValid: true }
expect(isContextValid(context, rules, options)).toBe(false)
})
it("returns false when no rules are valid", () => {
const rules = [invalidRule, invalidRule]
expect(isContextValid(context, rules)).toBe(false)
})
it("returns true when the 'gt' operator is valid", () => {
const rules = [
{
attribute: "attribute1",
operator: RuleOperator.GT,
value: "1", // 2 > 1
},
]
const context = { attribute1: "2" }
expect(isContextValid(context, rules)).toBe(true)
})
it("returns false when the 'gt' operator is invalid", () => {
const rules = [
{
attribute: "attribute1",
operator: RuleOperator.GT,
value: "0", // 0 > 0
},
]
const context = { attribute1: "0" }
expect(isContextValid(context, rules)).toBe(false)
})
it("returns true when the 'gte' operator is valid", () => {
const rules = [
{
attribute: "attribute1",
operator: RuleOperator.GTE,
value: "2", // 2 >= 2
},
]
const context = { attribute1: "2" }
expect(isContextValid(context, rules)).toBe(true)
})
it("returns false when the 'gte' operator is invalid", () => {
const rules = [
{
attribute: "attribute1",
operator: RuleOperator.GTE,
value: "3", // 2 >= 3
},
]
const context = { attribute1: "2" }
expect(isContextValid(context, rules)).toBe(false)
})
it("returns true when the 'lt' operator is valid", () => {
const rules = [
{
attribute: "attribute1",
operator: RuleOperator.LT,
value: "3", // 2 < 3
},
]
const context = { attribute1: "2" }
expect(isContextValid(context, rules)).toBe(true)
})
it("returns false when the 'lt' operator is invalid", () => {
const rules = [
{
attribute: "attribute1",
operator: RuleOperator.LT,
value: "2", // 2 < 2
},
]
const context = { attribute1: "2" }
expect(isContextValid(context, rules)).toBe(false)
})
it("returns true when the 'lte' operator is valid", () => {
const rules = [
{
attribute: "attribute1",
operator: RuleOperator.LTE,
value: "2", // 2 <= 2
},
]
const context = { attribute1: "2" }
expect(isContextValid(context, rules)).toBe(true)
})
// ... existing tests ...
it("returns false when the 'lte' operator is invalid", () => {
const rules = [
{
attribute: "attribute1",
operator: RuleOperator.LTE,
value: "1", // 2 <= 1
},
]
const context = { attribute1: "2" }
expect(isContextValid(context, rules)).toBe(false)
})
it("returns true when the 'in' operator is valid", () => {
const rules = [
{
attribute: "attribute1",
operator: RuleOperator.IN,
value: ["1", "2", "3"], // 2 in [1, 2, 3]
},
]
const context = { attribute1: "2" }
expect(isContextValid(context, rules)).toBe(true)
})
it("returns false when the 'in' operator is invalid", () => {
const rules = [
{
attribute: "attribute1",
operator: RuleOperator.IN,
value: ["1", "3", "4"], // 2 in [1, 3, 4]
},
]
const context = { attribute1: "2" }
expect(isContextValid(context, rules)).toBe(false)
})
it("returns true when the 'nin' operator is valid", () => {
const rules = [
{
attribute: "attribute1",
operator: RuleOperator.NIN,
value: ["1", "3", "4"], // 2 not in [1, 3, 4]
},
]
const context = { attribute1: "2" }
expect(isContextValid(context, rules)).toBe(true)
})
it("returns false when the 'nin' operator is invalid", () => {
const rules = [
{
attribute: "attribute1",
operator: RuleOperator.NIN,
value: ["1", "2", "3"], // 2 not in [1, 2, 3]
},
]
const context = { attribute1: "2" }
expect(isContextValid(context, rules)).toBe(false)
})
it("returns true when the 'ne' operator is valid", () => {
const rules = [
{
attribute: "attribute1",
operator: RuleOperator.NE,
value: "1", // 2 != 1
},
]
const context = { attribute1: "2" }
expect(isContextValid(context, rules)).toBe(true)
})
it("returns false when the 'ne' operator is invalid", () => {
const rules = [
{
attribute: "attribute1",
operator: RuleOperator.NE,
value: "2", // 2 != 2
},
]
const context = { attribute1: "2" }
expect(isContextValid(context, rules)).toBe(false)
})
it("returns true when the 'eq' operator is valid", () => {
const rules = [
{
attribute: "attribute1",
operator: RuleOperator.EQ,
value: "2", // 2 == 2
},
]
const context = { attribute1: "2" }
expect(isContextValid(context, rules)).toBe(true)
})
it("returns false when the 'eq' operator is invalid", () => {
const rules = [
{
attribute: "attribute1",
operator: RuleOperator.EQ,
value: "1", // 2 == 1
},
]
const context = { attribute1: "2" }
expect(isContextValid(context, rules)).toBe(false)
})
})

View File

@@ -0,0 +1 @@
export * from './utils'

View File

@@ -0,0 +1,172 @@
import {
isObject,
isString,
MedusaError,
pickValueFromObject,
RuleOperator,
} from "@medusajs/utils"
/**
* The rule engine here is kept inside the module as of now, but it could be moved
* to the utils package and be used across the different modules that provides context
* based rule filtering.
*
* TODO: discussion around that should happen at some point
*/
export type Rule = {
attribute: string
operator: Lowercase<keyof typeof RuleOperator>
value: string | string[] | null
}
export const availableOperators = Object.values(RuleOperator)
const isDate = (str: string) => {
return !isNaN(Date.parse(str))
}
const operatorsPredicate = {
in: (contextValue: string, ruleValue: string[]) =>
ruleValue.includes(contextValue),
nin: (contextValue: string, ruleValue: string[]) =>
!ruleValue.includes(contextValue),
eq: (contextValue: string, ruleValue: string) => contextValue === ruleValue,
ne: (contextValue: string, ruleValue: string) => contextValue !== ruleValue,
gt: (contextValue: string, ruleValue: string) => {
if (isDate(contextValue) && isDate(ruleValue)) {
return new Date(contextValue) > new Date(ruleValue)
}
return Number(contextValue) > Number(ruleValue)
},
gte: (contextValue: string, ruleValue: string) => {
if (isDate(contextValue) && isDate(ruleValue)) {
return new Date(contextValue) >= new Date(ruleValue)
}
return Number(contextValue) >= Number(ruleValue)
},
lt: (contextValue: string, ruleValue: string) => {
if (isDate(contextValue) && isDate(ruleValue)) {
return new Date(contextValue) < new Date(ruleValue)
}
return Number(contextValue) < Number(ruleValue)
},
lte: (contextValue: string, ruleValue: string) => {
if (isDate(contextValue) && isDate(ruleValue)) {
return new Date(contextValue) <= new Date(ruleValue)
}
return Number(contextValue) <= Number(ruleValue)
},
}
/**
* Validate contextValue context object from contextValue set of rules.
* By default, all rules must be valid to return true unless the option atLeastOneValidRule is set to true.
* @param context
* @param rules
* @param options
*/
export function isContextValid(
context: Record<string, any>,
rules: Rule[],
options: {
someAreValid: boolean
} = {
someAreValid: false,
}
): boolean {
const { someAreValid } = options
const loopComparator = someAreValid ? rules.some : rules.every
const predicate = (rule) => {
const { attribute, operator, value } = rule
const contextValue = pickValueFromObject(attribute, context)
return operatorsPredicate[operator](
contextValue,
value as string & string[]
)
}
return loopComparator.apply(rules, [predicate])
}
/**
* Validate contextValue rule object
* @param rule
*/
export function validateRule(rule: Record<string, unknown>): boolean {
if (!rule.attribute || !rule.operator || !rule.value) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Rule must have an attribute, an operator and a value"
)
}
if (!isString(rule.attribute)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Rule attribute must be a string"
)
}
if (!isString(rule.operator)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Rule operator must be a string"
)
}
if (!availableOperators.includes(rule.operator as RuleOperator)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Rule operator ${
rule.operator
} is not supported. Must be one of ${availableOperators.join(", ")}`
)
}
if (rule.operator === RuleOperator.IN || rule.operator === RuleOperator.NIN) {
if (!Array.isArray(rule.value)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Rule value must be an array for in/nin operators"
)
}
} else {
if (Array.isArray(rule.value) || isObject(rule.value)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Rule value must be a string, bool, number value for the selected operator ${rule.operator}`
)
}
}
return true
}
export function normalizeRulesValue<T extends Partial<Rule>>(rules: T[]): void {
rules.forEach((rule) => {
/**
* If a string is provided, then we don't want jsonb to convert to the primitive value based on the RFC
*/
if (rule.value === "true" || rule.value === "false") {
rule.value = rule.value === "true" ? '"true"' : '"false"'
}
return rule
})
}
export function validateAndNormalizeRules<T extends Partial<Rule>>(rules: T[]) {
rules.forEach(validateRule)
normalizeRulesValue(rules)
}
/**
* Validate contextValue set of rules
* @param rules
*/
export function validateRules(rules: Record<string, unknown>[]): boolean {
rules.forEach(validateRule)
return true
}